<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>chip8.rs: CHIP-8 Game Emulator in Rust for PineTime Smart Watch</title>

    
    <!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:image" content="https://lupyuen.github.io/images/chip8-invaders.jpg">
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<script src="../storage.js"></script><noscript>
    <link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
    #crate-search {
        background-image: url("../down-arrow.svg");
    }
    a {
        color: #77d;
    }
</style>
<!-- End scripts/rustdoc-header.html -->


</head>
<body class="rustdoc">
    <!--[if lte IE 8]>
    <div class="warning">
        This old browser is unsupported and will most likely display funky
        things.
    </div>
    <![endif]-->

        <!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->

    <!-- Begin Theme Picker -->
    <div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
        width="18" alt="Pick another theme!"></button>
        <div id="theme-choices"></div>
    </div>
    <script src="../theme.js"></script>
    <!-- Theme Picker -->

    <!-- End scripts/rustdoc-before.html -->
    

    <h1 class="title">chip8.rs: CHIP-8 Game Emulator in Rust for PineTime Smart Watch</h1>
    <nav id="TOC"><ul>
<li><a href="#start-the-chip-8emulator">1 Start the CHIP-8 Emulator</a><ul></ul></li>
<li><a href="#set-a-pixelcolour">2 Set a Pixel Colour</a><ul></ul></li>
<li><a href="#render-thedisplay">3 Render the Display</a><ul></ul></li>
<li><a href="#render-aregion">4 Render a Region</a><ul></ul></li>
<li><a href="#render-ablock">5 Render a Block</a><ul></ul></li>
<li><a href="#iterate-pixels-in-ablock">6 Iterate Pixels in a Block</a><ul></ul></li>
<li><a href="#convert-colours">7 Convert Colours</a><ul></ul></li>
<li><a href="#handle-touch-events">8 Handle Touch Events</a><ul></ul></li>
<li><a href="#chip-8-emulator-task">9 CHIP-8 Emulator Task</a><ul></ul></li>
<li><a href="#build-and-run-the-emulator">10 Build and Run the Emulator</a><ul></ul></li>
<li><a href="#distort-the-chip-8-rendering">11 Distort the CHIP-8 Rendering</a><ul></ul></li>
<li><a href="#interpolate-the-sphere-to-square-mapping-physical--virtual">12 Interpolate the Sphere to Square mapping (Physical → Virtual)</a><ul></ul></li>
<li><a href="#interpolate-the-square-to-sphere-mapping-virtual--physical">13 Interpolate the Square to Sphere mapping (Virtual → Physical)</a><ul></ul></li>
<li><a href="#validate-the-square-to-sphere-interpolation">14 Validate the Square to Sphere Interpolation</a><ul></ul></li>
<li><a href="#map-physical-pixels-to-virtualpixels">15 Map Physical Pixels to Virtual Pixels</a><ul></ul></li>
<li><a href="#map-virtual-pixels-to-physicalpixels">16 Map Virtual Pixels to Physical Pixels</a><ul></ul></li>
<li><a href="#iterate-curvedpixels">17 Iterate Curved Pixels</a><ul></ul></li>
<li><a href="#lookup-table-size">18 Lookup Table Size</a><ul></ul></li>
<li><a href="#further-reading">19 Further Reading</a><ul></ul></li></ul></nav><p><img src="https://lupyuen.github.io/images/chip8-invaders.jpg" alt="Space Invaders running on CHIP-8 Emulator on PineTime Smart Watch" /></p>
<p><em>Space Invaders running on CHIP-8 Emulator on PineTime Smart Watch</em></p>
<p>Running Retro Games with Rust is not that hard on <a href="https://wiki.pine64.org/index.php/PineTime">PineTime Smart Watch</a>. Here's how I ported a CHIP-8 Game Emulator to PineTime...</p>
<p><a href="http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/">More about CHIP-8</a></p>
<p><em>Are you keen to use a Retro Game as a PineTime Watch Face? <a href="https://twitter.com/MisterTechBlog">Lemme know!</a></em></p>
<h1 id="start-the-chip-8emulator" class="section-header"><a href="#start-the-chip-8emulator">1 Start the CHIP-8 Emulator</a></h1>
<p>We're using the <a href="https://github.com/YushiOMOTE/libchip8">libchip8 CHIP-8 Emulator for Rust</a>. To start the emulator, we load the ROM file for the CHIP-8 game into memory, and call the Emulator to start the game...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">///  Run the emulator</span>
<span class="kw">extern</span> <span class="string">&quot;C&quot;</span> <span class="kw">fn</span> <span class="ident">task_func</span>(<span class="ident">_arg</span>: <span class="ident">Ptr</span>) {    
    <span class="comment">//  Create the hardware API for rendering the emulator</span>
    <span class="kw">let</span> <span class="ident">hardware</span> <span class="op">=</span> <span class="ident">Hardware</span>::<span class="ident">new</span>();

    <span class="comment">//  Create the emulator</span>
    <span class="kw">let</span> <span class="ident">chip8</span> <span class="op">=</span> <span class="ident">libchip8</span>::<span class="ident">Chip8</span>::<span class="ident">new</span>(<span class="ident">hardware</span>);

    <span class="comment">//  Load the emulator ROM</span>
    <span class="kw">let</span> <span class="ident">rom</span> <span class="op">=</span> <span class="macro">include_bytes</span><span class="macro">!</span>(<span class="string">&quot;../roms/blinky.ch8&quot;</span>);

    <span class="comment">//  Run the emulator ROM. This will block until emulator terminates</span>
    <span class="ident">chip8</span>.<span class="ident">run</span>(<span class="ident">rom</span>);

    <span class="comment">//  Should not come here</span>
    <span class="macro">assert</span><span class="macro">!</span>(<span class="bool-val">false</span>, <span class="string">&quot;CHIP8 should not end&quot;</span>);
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L78-L98</em></p>
<p>Note the neat syntax used in Rust to load binary files into memory...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
    <span class="comment">//  Load the emulator ROM</span>
    <span class="kw">let</span> <span class="ident">rom</span> <span class="op">=</span> <span class="macro">include_bytes</span><span class="macro">!</span>(<span class="string">&quot;../roms/blinky.ch8&quot;</span>);</pre></div>
<p><code>blinky.ch8</code> is a binary file that contains the program and data for the Blinky CHIP-8 game. By calling the <code>include_bytes!</code> macro, we load the entire binary file into memory as a Rust static memory object.</p>
<p><a href="https://github.com/dmatlack/chip8/tree/master/roms">Here's a list of available CHIP-8 ROM files</a> that we may download into the <a href="https://github.com/lupyuen/pinetime-rust-mynewt/tree/master/rust/app/roms"><code>rust/app/roms</code> folder</a> </p>
<p>To preview the CHIP-8 games in a web browser, <a href="http://mir3z.github.io/chip8-emu/">use this browser-based CHIP-8 Emulator</a>.</p>
<p>How is <code>Hardware</code> used? We'll find out next...</p>
<p><img src="https://lupyuen.github.io/images/chip8-blinky.jpg" alt="Blinky running on CHIP-8 Emulator on PineTime Smart Watch" /></p>
<p><em>Blinky running on CHIP-8 Emulator on PineTime Smart Watch</em></p>
<h1 id="set-a-pixelcolour" class="section-header"><a href="#set-a-pixelcolour">2 Set a Pixel Colour</a></h1>
<p><code>libchip8</code> is a clever CHIP-8 Emulator that supports all kinds of platforms, including Windows. <em>How does it do that?</em></p>
<p><code>libchip8</code> abstracts all platform-specific operations (like Screen Updates) into the <code>Hardware</code> trait.  Here's how we implement the <code>Hardware</code> trait on PineTime to set a screen pixel on or off...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="kw">impl</span> <span class="ident">libchip8</span>::<span class="ident">Hardware</span> <span class="kw">for</span> <span class="ident">Hardware</span> {
    <span class="doccomment">/// Set the color of a pixel in the screen. true for white, and false for black.</span>
    <span class="kw">fn</span> <span class="ident">vram_set</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>, <span class="ident">x</span>: <span class="ident">usize</span>, <span class="ident">y</span>: <span class="ident">usize</span>, <span class="ident">color</span>: <span class="ident">bool</span>) {
        <span class="macro">assert</span><span class="macro">!</span>(<span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">SCREEN_WIDTH</span>,  <span class="string">&quot;x overflow&quot;</span>);  <span class="comment">//  x must be 0 to 63</span>
        <span class="macro">assert</span><span class="macro">!</span>(<span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">SCREEN_HEIGHT</span>, <span class="string">&quot;y overflow&quot;</span>);  <span class="comment">//  y must be 0 to 31</span>
        <span class="kw">let</span> <span class="ident">i</span> <span class="op">=</span> <span class="ident">x</span> <span class="op">+</span> <span class="ident">y</span> <span class="op">*</span> <span class="ident">SCREEN_WIDTH</span>;              <span class="comment">//  index into screen buffer</span>
        <span class="kw">unsafe</span> { <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] <span class="op">=</span>  <span class="comment">//  Screen the screen buffer to...</span>
            <span class="kw">if</span> <span class="ident">color</span> { <span class="number">255</span> }         <span class="comment">//  White pixel</span>
            <span class="kw">else</span>     {   <span class="number">0</span> }         <span class="comment">//  Black pixel</span>
        };
        <span class="comment">//  Remember the boundary of the screen region to be updated</span>
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">update_left</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">update_right</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span>
            <span class="self">self</span>.<span class="ident">update_top</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">update_bottom</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> {
            <span class="self">self</span>.<span class="ident">update_left</span> <span class="op">=</span> <span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>; <span class="self">self</span>.<span class="ident">update_right</span>  <span class="op">=</span> <span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>;
            <span class="self">self</span>.<span class="ident">update_top</span>  <span class="op">=</span> <span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>; <span class="self">self</span>.<span class="ident">update_bottom</span> <span class="op">=</span> <span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>;
        }
        <span class="comment">//  If this pixel is outside the the boundary of the screen region to be updated, extend the boundary</span>
        <span class="kw">if</span> (<span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>) <span class="op">&lt;</span> <span class="self">self</span>.<span class="ident">update_left</span>   { <span class="self">self</span>.<span class="ident">update_left</span> <span class="op">=</span> <span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>;   }
        <span class="kw">if</span> (<span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>) <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">update_right</span>  { <span class="self">self</span>.<span class="ident">update_right</span> <span class="op">=</span> <span class="ident">x</span> <span class="kw">as</span> <span class="ident">u8</span>;  }
        <span class="kw">if</span> (<span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>) <span class="op">&lt;</span> <span class="self">self</span>.<span class="ident">update_top</span>    { <span class="self">self</span>.<span class="ident">update_top</span> <span class="op">=</span> <span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>;    }
        <span class="kw">if</span> (<span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>) <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">update_bottom</span> { <span class="self">self</span>.<span class="ident">update_bottom</span> <span class="op">=</span> <span class="ident">y</span> <span class="kw">as</span> <span class="ident">u8</span>; }
    }</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L169-L198</em></p>
<p><em>(<code>u8</code> means unsigned byte; <code>usize</code> is similar to <code>size_t</code> in C )</em></p>
<p>The CHIP-8 Emulator has a simple screen layout: 64 rows, 32 columns, 1-bit colour (black or white). <code>vram_set</code> updates the pixel colour in a greyscale memory buffer named <code>SCREEN_BUFFER</code> that's only 2 KB (64 rows of 32 bytes)...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// CHIP8 Virtual Screen size, in Virtual Pixels</span>
<span class="kw">const</span> <span class="ident">SCREEN_WIDTH</span>:  <span class="ident">usize</span> <span class="op">=</span> <span class="number">64</span>;
<span class="kw">const</span> <span class="ident">SCREEN_HEIGHT</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">32</span>;
<span class="doccomment">/// CHIP8 Virtual Screen Buffer, 8-bit greyscale (from black=0 to white=255) per Virtual Pixel.</span>
<span class="doccomment">/// The greyscale is mapped to 16-bit colour for display.</span>
<span class="kw">static</span> <span class="kw-2">mut</span> <span class="ident">SCREEN_BUFFER</span>: [<span class="ident">u8</span>; <span class="ident">SCREEN_WIDTH</span> <span class="op">*</span> <span class="ident">SCREEN_HEIGHT</span>] <span class="op">=</span> [<span class="number">0</span>; <span class="ident">SCREEN_WIDTH</span> <span class="op">*</span> <span class="ident">SCREEN_HEIGHT</span>];  <span class="comment">//  2 KB (64 rows of 32 bytes), u8 means unsigned byte</span></pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L19-L37</em></p>
<p><em>Why did we allocate 8 bits per pixel in <code>SCREEN_BUFFER</code>?</em> </p>
<p>So that we can implement interesting colour effects. (We'll cover this later) We actually update <code>SCREEN_BUFFER</code> with a greyscale colour like this...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="comment">//  color is true when emulator draws a white pixel, black otherwise</span>
<span class="kw">unsafe</span> { <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] <span class="op">=</span> 
    <span class="kw">if</span> <span class="ident">color</span> {
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="number">255</span> }  <span class="comment">//  Brighter colour when emulator is active</span>
        <span class="kw">else</span> { <span class="number">200</span> }                    <span class="comment">//  Darker colour for initial screen</span>
    } 
    <span class="kw">else</span> { 
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="number">127</span> }  <span class="comment">//  Fade to black</span>
        <span class="kw">else</span> { <span class="number">0</span> }                      <span class="comment">//  Black for initial screen                 </span>
    }  
};</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L169-L198</em></p>
<p><em>Something seems to be missing... <code>vram_set</code> updates a screen buffer in memory... But we haven't actually updated the PineTime display!</em></p>
<p><code>vram_set</code> is called every time the Emulator paints a pixel. Instead of refreshing the PineTime display pixel by pixel, we update the display by <strong>Sprite</strong> instead. </p>
<p><em>What's a Sprite?</em></p>
<p>No not the lemon-lime drink... It's the graphic that moves around in a game. In the Blinky / Pac-Man game, Pac-Man and the Ghosts are rendered as Sprites.</p>
<p><em>How do we know when a Sprite has been drawn?</em> </p>
<p>We detect that in the <code>sched</code> function, where we update the PineTime display too.</p>
<h1 id="render-thedisplay" class="section-header"><a href="#render-thedisplay">3 Render the Display</a></h1>
<p><em>How does CHIP-8 run game programs and render Sprites?</em></p>
<p>Think of CHIP-8 as an old-style home computer from the 1980s. It executes simple 8-bit Instructions (Opcodes), reading and writing data to CPU registers and RAM.</p>
<p>CHIP-8 has a unique Instruction that's not found in computers from the 1980s... An Instruction that <strong>renders Sprites.</strong> Since CHIP-8 renders Sprites as an CHIP-8 Instruction, we should update the PineTime screen only when the CHIP-8 Instruction has completed.</p>
<p>The CHIP-8 Emulator provides a convenient hook for that... It calls <code>sched</code> after executing every CHIP-8 Instruction...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="kw">impl</span> <span class="ident">libchip8</span>::<span class="ident">Hardware</span> <span class="kw">for</span> <span class="ident">Hardware</span> {
    <span class="doccomment">/// Called in every step; return true for shutdown.</span>
    <span class="kw">fn</span> <span class="ident">sched</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">bool</span> {
        <span class="comment">//  If no screen update, return</span>
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">update_left</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">update_right</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span>
            <span class="self">self</span>.<span class="ident">update_top</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">update_bottom</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> { <span class="kw">return</span> <span class="bool-val">false</span>; }

        <span class="comment">//  If emulator is preparing the initial screen, refresh the screen later</span>
        <span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="kw">return</span> <span class="bool-val">false</span>; }

        <span class="comment">//  If emulator is not ready to accept input, refresh the screen later</span>
        <span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_checking_input</span> { <span class="kw">return</span> <span class="bool-val">false</span>; }
        <span class="self">self</span>.<span class="ident">is_checking_input</span> <span class="op">=</span> <span class="bool-val">false</span>;

        <span class="comment">//  Tickle the watchdog so that the Watchdog Timer doesn&#39;t expire. Mynewt assumes the process is hung if we don&#39;t tickle the watchdog.</span>
        <span class="kw">unsafe</span> { <span class="ident">hal_watchdog_tickle</span>() };

        <span class="comment">//  Sleep a while to allow other tasks to run, e.g. SPI background task</span>
        <span class="kw">unsafe</span> { <span class="ident">os</span>::<span class="ident">os_time_delay</span>(<span class="number">1</span>) };

        <span class="comment">//  Render the updated region</span>
        <span class="ident">render_region</span>(
            <span class="self">self</span>.<span class="ident">update_left</span>,
            <span class="self">self</span>.<span class="ident">update_top</span>,
            <span class="self">self</span>.<span class="ident">update_right</span>,
            <span class="self">self</span>.<span class="ident">update_bottom</span>
        );

        <span class="comment">//  Reset the screen region to be updated</span>
        <span class="self">self</span>.<span class="ident">update_left</span>   <span class="op">=</span> <span class="number">0</span>;
        <span class="self">self</span>.<span class="ident">update_top</span>    <span class="op">=</span> <span class="number">0</span>;
        <span class="self">self</span>.<span class="ident">update_right</span>  <span class="op">=</span> <span class="number">0</span>;
        <span class="self">self</span>.<span class="ident">update_bottom</span> <span class="op">=</span> <span class="number">0</span>;

        <span class="comment">//  Return false to indicate no shutdown</span>
        <span class="bool-val">false</span>
    }</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L231-L268</em></p>
<p>Instead of updating the entire PineTime display, we update only the rectangular portion that has been changed, by calling <code>render_region</code>.</p>
<p><em>(Recall that screen updates are tracked by <code>vram_set</code>)</em></p>
<p>Updating the PineTime display really slows down the CHIP-8 Emulator, so we defer all display updates until absolutely necessary.</p>
<p><em>When is it absolutely necessary to update the PineTime display?</em></p>
<p><em>That's when the game has rendered something and is checking whether the player has pressed any buttons</em></p>
<p>Thus we have these conditions to defer the PineTime display updates in <code>sched</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="comment">//  If emulator is preparing the initial screen, refresh the screen later</span>
<span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="kw">return</span> <span class="bool-val">false</span>; }

<span class="comment">//  If emulator is not ready to accept input, refresh the screen later</span>
<span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_checking_input</span> { <span class="kw">return</span> <span class="bool-val">false</span>; }
<span class="self">self</span>.<span class="ident">is_checking_input</span> <span class="op">=</span> <span class="bool-val">false</span>;</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L231-L268</em></p>
<p><code>is_interactive</code> and <code>is_checking_input</code> are flags set in the <code>key</code> function, which is called whenever the CHIP-8 Emulator is checking for button presses.</p>
<p>These simple conditions for defering the PineTime rendering are extremely effective. They make the PineTime CHIP-8 Emulator refresh some screens quicker than other versions of the CHIP-8 Emulator.</p>
<p><em>(Compare the loading screen for Blinky on PineTime vs other platforms)</em></p>
<h1 id="render-aregion" class="section-header"><a href="#render-aregion">4 Render a Region</a></h1>
<p>Previously in <code>sched</code> we have identified the rectangular region of the PineTime display to be updated. We could call <code>render_block</code> to render the entire region to the PineTime display in a <strong>single SPI operation</strong> like this...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Render the Virtual Screen region</span>
<span class="kw">fn</span> <span class="ident">render_region</span>(<span class="ident">left</span>: <span class="ident">u8</span>, <span class="ident">top</span>: <span class="ident">u8</span>, <span class="ident">right</span>: <span class="ident">u8</span>, <span class="ident">bottom</span>: <span class="ident">u8</span>) {
    <span class="comment">//  Get the physical bounding box width and height</span>
    <span class="kw">let</span> <span class="ident">physical_box</span>    <span class="op">=</span> <span class="ident">get_bounding_box</span>(<span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span>);  <span class="comment">//  Returns (left,top,right,bottom)</span>
    <span class="kw">let</span> <span class="ident">physical_width</span>  <span class="op">=</span> (<span class="ident">physical_box</span>.<span class="number">2</span> <span class="op">-</span> <span class="ident">physical_box</span>.<span class="number">0</span> <span class="op">+</span> <span class="number">1</span>) <span class="kw">as</span> <span class="ident">usize</span>;
    <span class="kw">let</span> <span class="ident">physical_height</span> <span class="op">=</span> (<span class="ident">physical_box</span>.<span class="number">3</span> <span class="op">-</span> <span class="ident">physical_box</span>.<span class="number">1</span> <span class="op">+</span> <span class="number">1</span>) <span class="kw">as</span> <span class="ident">usize</span>;
    <span class="comment">//  If the update region is small, render with a single block</span>
    <span class="kw">if</span> <span class="ident">physical_width</span> <span class="op">+</span> <span class="ident">physical_height</span> <span class="op">&lt;</span><span class="op">=</span> (<span class="ident">BLOCK_WIDTH</span> <span class="op">*</span> <span class="ident">PIXEL_WIDTH</span>) <span class="op">+</span> (<span class="ident">BLOCK_HEIGHT</span> <span class="op">*</span> <span class="ident">PIXEL_HEIGHT</span>) {  <span class="comment">//  Will not overflow SPI buffer</span>
        <span class="ident">render_block</span>(<span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span>);
    } <span class="kw">else</span> {
        ...</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L271-L304</em></p>
<p><em>...But it won't always work!</em></p>
<p>Our Rust display driver for PineTime has a buffer size of <strong>8 KB.</strong></p>
<p>Since one pixel on the PineTime display has 16-bit colour, that means we can (roughly) transmit at most <strong>4,096 pixels in a single SPI operation.</strong></p>
<p><em>If we need to update the <strong>entire CHIP-8 Emulator screen</strong>, how many pixels would we need to transmit?</em></p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// CHIP8 Physical Screen size, in Physical Pixels</span>
<span class="kw">const</span> <span class="ident">PHYSICAL_WIDTH</span>:  <span class="ident">usize</span> <span class="op">=</span> <span class="number">240</span>;
<span class="kw">const</span> <span class="ident">PHYSICAL_HEIGHT</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">200</span>;

<span class="doccomment">/// CHIP8 Virtual Screen size, in Virtual Pixels</span>
<span class="kw">const</span> <span class="ident">SCREEN_WIDTH</span>:  <span class="ident">usize</span> <span class="op">=</span> <span class="number">64</span>;
<span class="kw">const</span> <span class="ident">SCREEN_HEIGHT</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">32</span>;

<span class="doccomment">/// CHIP8 Virtual Pixel size, in Physical Pixels</span>
<span class="kw">const</span> <span class="ident">PIXEL_WIDTH</span>:  <span class="ident">usize</span> <span class="op">=</span> <span class="number">3</span>;  <span class="comment">//  One Virtual Pixel = 3 x 5 Physical Pixels</span>
<span class="kw">const</span> <span class="ident">PIXEL_HEIGHT</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">5</span>;</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L19-L37</em></p>
<p>To update the entite CHIP-8 display, we would need to transmit <strong>48,000 pixels</strong> over SPI (<code>PHYSICAL_WIDTH</code> * <code>PHYSICAL_HEIGHT</code>)... Waaaaay too many pixels!</p>
<p><em>Why so many pixels? Isn't the CHIP-8 screen size only 64 x 32? (<code>SCREEN_WIDTH</code> by <code>SCREEN_HEIGHT</code>)</em></p>
<p>Yeah but we need to <strong>stretch every CHIP-8 Virtual Pixel into 15 PineTime Physical Pixels</strong> to fill the PineTime display!</p>
<p><em>(That's 3 * 5... <code>PIXEL_WIDTH</code> * <code>PIXEL_HEIGHT</code>)</em></p>
<p>Unfortunately the PineTime display controller (ST7789) doesn't handle stretching, so we need to do the stretching ourselves.</p>
<p><em>(But fret not... There's something interesting we shall see later... We can do curved stretching! Just like making pizza or roti prata!)</em></p>
<p>Thus to prevent the SPI buffer from overflowing, we update the screen in blocks of 32 by 5 Virtual Pixels on CHIP-8... (Or 96 by 25 Physical Pixels on the PineTime display)</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// CHIP8 Virtual Block size. We render the CHIP8 Virtual Screen in blocks of Virtual Pixels, without overflowing the SPI buffer.</span>
<span class="doccomment">/// PendingDataSize in SPI is 8192. (BLOCK_WIDTH * PIXEL_WIDTH * BLOCK_HEIGHT * PIXEL_HEIGHT) * 2 must be less than PendingDataSize</span>
<span class="kw">const</span> <span class="ident">BLOCK_WIDTH</span>:  <span class="ident">usize</span> <span class="op">=</span> <span class="number">32</span>;
<span class="kw">const</span> <span class="ident">BLOCK_HEIGHT</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">5</span>;  <span class="comment">//  Letter height</span></pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L27-L33</em></p>
<p><em>(Hmmm there's something wrong with the math here...)</em></p>
<p>Here's how we break the rendering region into smaller blocks to be rendered by <code>render_block</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Render the Virtual Screen region</span>
<span class="kw">fn</span> <span class="ident">render_region</span>(<span class="ident">left</span>: <span class="ident">u8</span>, <span class="ident">top</span>: <span class="ident">u8</span>, <span class="ident">right</span>: <span class="ident">u8</span>, <span class="ident">bottom</span>: <span class="ident">u8</span>) {
    ...
    <span class="comment">//  If the update region is small, render with a single block</span>
    <span class="kw">if</span> <span class="ident">physical_width</span> <span class="op">+</span> <span class="ident">physical_height</span> <span class="op">&lt;</span><span class="op">=</span> (<span class="ident">BLOCK_WIDTH</span> <span class="op">*</span> <span class="ident">PIXEL_WIDTH</span>) <span class="op">+</span> (<span class="ident">BLOCK_HEIGHT</span> <span class="op">*</span> <span class="ident">PIXEL_HEIGHT</span>) {
        ...
    } <span class="kw">else</span> {
        <span class="comment">//  If the update region is too big for a single block, break the region into blocks and render</span>
        <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">x</span> <span class="op">=</span> <span class="ident">left</span>;
        <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">y</span> <span class="op">=</span> <span class="ident">top</span>;
        <span class="kw">loop</span> {
            <span class="kw">let</span> <span class="ident">block_right</span>  <span class="op">=</span> (<span class="ident">x</span> <span class="op">+</span> <span class="ident">BLOCK_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>).<span class="ident">min</span>(<span class="ident">right</span>);
            <span class="kw">let</span> <span class="ident">block_bottom</span> <span class="op">=</span> (<span class="ident">y</span> <span class="op">+</span> <span class="ident">BLOCK_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>).<span class="ident">min</span>(<span class="ident">bottom</span>);

            <span class="kw">let</span> <span class="ident">physical_box</span>    <span class="op">=</span> <span class="ident">get_bounding_box</span>(<span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span>);  <span class="comment">//  Returns (left,top,right,bottom)</span>
            <span class="kw">let</span> <span class="ident">physical_width</span>  <span class="op">=</span> (<span class="ident">physical_box</span>.<span class="number">2</span> <span class="op">-</span> <span class="ident">physical_box</span>.<span class="number">0</span> <span class="op">+</span> <span class="number">1</span>) <span class="kw">as</span> <span class="ident">usize</span>;
            <span class="kw">let</span> <span class="ident">physical_height</span> <span class="op">=</span> (<span class="ident">physical_box</span>.<span class="number">3</span> <span class="op">-</span> <span class="ident">physical_box</span>.<span class="number">1</span> <span class="op">+</span> <span class="number">1</span>) <span class="kw">as</span> <span class="ident">usize</span>;
            <span class="ident">render_block</span>(<span class="ident">x</span>, <span class="ident">y</span>,
                <span class="ident">block_right</span>,
                <span class="ident">block_bottom</span>
            );  <span class="comment">//  Will not overflow SPI buffer</span>
            <span class="ident">x</span> <span class="op">+</span><span class="op">=</span> <span class="ident">BLOCK_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span>;
            <span class="kw">if</span> <span class="ident">x</span> <span class="op">&gt;</span> <span class="ident">right</span> {
                <span class="ident">x</span> <span class="op">=</span> <span class="ident">left</span>;
                <span class="ident">y</span> <span class="op">+</span><span class="op">=</span> <span class="ident">BLOCK_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span>;
                <span class="kw">if</span> <span class="ident">y</span> <span class="op">&gt;</span> <span class="ident">bottom</span> { <span class="kw">break</span>; }
            }
        }
    }
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L271-L304</em></p>
<p>The function <code>get_bounding_box(left, top, right, bottom)</code> simply returns a Rust tuple <code>(left, top, right, bottom)</code> that may be accessed by using the <code>.0</code>, <code>.1</code>, <code>.2</code> and <code>.3</code> notation (shown above like <code>physical_box.0</code>).</p>
<p><code>get_bounding_box</code> doesn't do much now... But it will become very interesting later when we stretch the CHIP-8 pixels in a curvy way.</p>
<p><em>(Oh yes I love Rust tuples! As much as roti prata!)</em></p>
<h1 id="render-ablock" class="section-header"><a href="#render-ablock">5 Render a Block</a></h1>
<p>Now we're ready to render a block of CHIP-8 Virtual Pixels (that has been checked by <code>render_region</code> and won't overflow the SPI buffer)...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Render the Virtual Block</span>
<span class="kw">fn</span> <span class="ident">render_block</span>(<span class="ident">left</span>: <span class="ident">u8</span>, <span class="ident">top</span>: <span class="ident">u8</span>, <span class="ident">right</span>: <span class="ident">u8</span>, <span class="ident">bottom</span>: <span class="ident">u8</span>) {
    <span class="comment">//  Create an iterator for the Physical Pixels to be rendered</span>
    <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">block</span> <span class="op">=</span> <span class="ident">PixelIterator</span>::<span class="ident">new</span>(
        <span class="ident">left</span>, <span class="ident">top</span>, 
        <span class="ident">right</span>, <span class="ident">bottom</span>,
    );
    <span class="comment">//  Get the Physical Pixel dimensions of the Virtual Pixels</span>
    <span class="kw">let</span> (<span class="ident">left_physical</span>, <span class="ident">top_physical</span>, <span class="ident">right_physical</span>, <span class="ident">bottom_physical</span>) <span class="op">=</span> <span class="ident">block</span>.<span class="ident">get_window</span>();
    <span class="comment">//  Render the block via the iterator</span>
    <span class="ident">druid</span>::<span class="ident">set_display_pixels</span>(<span class="ident">left_physical</span> <span class="kw">as</span> <span class="ident">u16</span>, <span class="ident">top_physical</span> <span class="kw">as</span> <span class="ident">u16</span>, <span class="ident">right_physical</span> <span class="kw">as</span> <span class="ident">u16</span>, <span class="ident">bottom_physical</span> <span class="kw">as</span> <span class="ident">u16</span>,
        <span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="ident">block</span>
    ).<span class="ident">expect</span>(<span class="string">&quot;set pixels failed&quot;</span>);    
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L306-L319</em></p>
<p><em>Rendering a block of CHIP-8 Virtual Pixels looks suspiciously simple... What's an Iterator?</em></p>
<p>A Rust Iterator loops over individual values in a sequence of values. (It's often used in <code>for</code> loops)</p>
<p>Here we create a Rust Iterator that loops over individual Physical PineTime Pixels to be rendered (based on the Virtual CHIP-8 Block that's passed in).</p>
<p>The values returned by the Rust Iterator are 16-bit colour values. Our Rust display driver for PineTime calls the Rust Iterator to enumerate all the 16-bit colour values and blast all values in a single SPI operation. Super efficient!</p>
<h1 id="iterate-pixels-in-ablock" class="section-header"><a href="#iterate-pixels-in-ablock">6 Iterate Pixels in a Block</a></h1>
<p>Here's the implementation of our iterator that enumerates all Physical Pixel Colours within a Virtual Pixel Block that's defined by the Virtual <code>(x, y)</code> Coordinates from <code>(block_left, block_top)</code> to <code>(block_right, block_bottom)</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Implement the Iterator for Virtual Pixels in a Virtual Block</span>
<span class="kw">impl</span> <span class="ident">Iterator</span> <span class="kw">for</span> <span class="ident">PixelIterator</span> {
    <span class="doccomment">/// This Iterator returns Physical Pixel colour words (16-bit)</span>
    <span class="kw">type</span> <span class="ident">Item</span> <span class="op">=</span> <span class="ident">u16</span>;

    <span class="doccomment">/// Return the next Physical Pixel colour</span>
    <span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">not</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;chip8_curve&quot;</span>))]</span>  <span class="comment">//  If we are not rendering CHIP8 Emulator as curved surface...</span>
    <span class="kw">fn</span> <span class="ident">next</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="prelude-ty">Option</span><span class="op">&lt;</span><span class="self">Self</span>::<span class="ident">Item</span><span class="op">&gt;</span> {
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">y</span> <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">block_bottom</span> { <span class="kw">return</span> <span class="prelude-val">None</span>; }  <span class="comment">//  No more Physical Pixels</span>

        <span class="comment">//  Get the 16-bit Physical Colour for the Virtual Pixel at (x, y)</span>
        <span class="kw">let</span> <span class="ident">color</span> <span class="op">=</span> <span class="self">self</span>.<span class="ident">get_color</span>();

        <span class="comment">//  Update (x, y) to the next pixel left to right, then top to bottom...</span>
        <span class="comment">//  Loop over every stretched horizontal Physical Pixel (x_offset) from 0 to PIXEL_WIDTH - 1</span>
        <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">PIXEL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> {
            <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">=</span> <span class="number">0</span>;

            <span class="comment">//  Loop over every Virtual Pixel (x) from block_left to block_right</span>
            <span class="self">self</span>.<span class="ident">x</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
            <span class="kw">if</span> <span class="self">self</span>.<span class="ident">x</span> <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">block_right</span> {
                <span class="self">self</span>.<span class="ident">x</span> <span class="op">=</span> <span class="self">self</span>.<span class="ident">block_left</span>;

                <span class="comment">//  Loop over every stretched vertical Vertical Pixel (y_offset) from 0 to PIXEL_HEIGHT - 1</span>
                <span class="self">self</span>.<span class="ident">y_offset</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
                <span class="kw">if</span> <span class="self">self</span>.<span class="ident">y_offset</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">PIXEL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> {
                    <span class="self">self</span>.<span class="ident">y_offset</span> <span class="op">=</span> <span class="number">0</span>;

                    <span class="comment">//  Loop over every Virtual Pixel (y) from block_top to block_bottom</span>
                    <span class="self">self</span>.<span class="ident">y</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
                }
            }
        }
        <span class="comment">//  Return the Physical Pixel color</span>
        <span class="kw">return</span> <span class="prelude-val">Some</span>(<span class="ident">color</span>);
    }</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L408-L455</em></p>
<p>The Iterator returns the next Physical Pixel Colour, as defined by <code>self.x</code> (Current Virtual Column) and <code>self.y</code> (Current Virtual Row). </p>
<p>Let's look at function <code>get_color</code>, which maps greyscale CHIP-8 Virtual Colours to 16-bit PineTime Physical Colours...</p>
<h1 id="convert-colours" class="section-header"><a href="#convert-colours">7 Convert Colours</a></h1>
<p>CHIP-8 doesn't support colour... Everything is rendered in black and white... Only Two Shades of Grey!</p>
<p><em>What if we spice up CHIP-8 games with a dash of colour? How shall we colourise a CHIP-8 game that doesn't know anything about colour?</em></p>
<p>By hooking on to the <code>key</code> function, we know when the game is first seeking input... Everything that the game renders after startup and up till the first call to <code>key</code> is most likely the Initial Loading Screen. </p>
<p>Thus in the <code>key</code> function we flag <code>is_interactive</code> as <code>true</code> at the first call to <code>key</code>.</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="kw">impl</span> <span class="ident">libchip8</span>::<span class="ident">Hardware</span> <span class="kw">for</span> <span class="ident">Hardware</span> {
    <span class="doccomment">/// Check if the key is pressed.</span>
    <span class="kw">fn</span> <span class="ident">key</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>, <span class="ident">key</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">bool</span> {
        <span class="comment">//  key is 0-9 for keys &quot;0&quot; to &quot;9&quot;, 0xa-0xf to keys &quot;A&quot; to &quot;F&quot;</span>
        <span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_interactive</span> {
            <span class="self">self</span>.<span class="ident">is_interactive</span> <span class="op">=</span> <span class="bool-val">true</span>;
        }
        <span class="self">self</span>.<span class="ident">is_checking_input</span> <span class="op">=</span> <span class="bool-val">true</span>;
        <span class="comment">//  Compare the key with the last touch event</span>
        <span class="kw">if</span> <span class="kw">unsafe</span> { <span class="ident">KEY_PRESSED</span> <span class="op">=</span><span class="op">=</span> <span class="prelude-val">Some</span>(<span class="ident">key</span>) } {
            <span class="kw">unsafe</span> { <span class="ident">KEY_PRESSED</span> <span class="op">=</span> <span class="prelude-val">None</span> };  <span class="comment">//  Clear the touch event</span>
            <span class="kw">return</span> <span class="bool-val">true</span>;
        }
        <span class="bool-val">false</span>
    }</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L133-L147</em></p>
<p><em>Since we can identify the Initial Loading Screen... Let's colour that screen!</em></p>
<p>Remember this code from <code>vram_set</code>?</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="comment">//  color is true when emulator draws a white pixel, black otherwise</span>
<span class="kw">unsafe</span> { <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] <span class="op">=</span> 
    <span class="kw">if</span> <span class="ident">color</span> {                          <span class="comment">//  If emulator draws a white pixel...</span>
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="number">255</span> }  <span class="comment">//  Brighter colour when emulator is active</span>
        <span class="kw">else</span> { <span class="number">200</span> }                    <span class="comment">//  Darker colour for initial screen</span>
    } 
    <span class="kw">else</span> {                              <span class="comment">//  If emulator draws a black pixel...</span>
        ...</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L169-L198</em></p>
<p>Instead of plain black and white, our CHIP-8 Virtual Screen Buffer now stores 8-bit greyscale... 256 Shades of Grey!</p>
<p>Through the <code>is_interactive</code> flag, we may now paint the Initial Loading Screen as greyscale <code>200</code>. Other pixels drawn after the Initial Loading Screen will be set to greyscale <code>255</code>.</p>
<p>Greyscale <code>200</code> is converted into a greenish hue, while greyscale <code>255</code> is converted to bright white...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Convert the Virtual Colour (8-bit greyscale) to 16-bit Physical Pixel Colour</span>
<span class="kw">fn</span> <span class="ident">convert_color</span>(<span class="ident">grey</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">u16</span> {
    <span class="kw">match</span> <span class="ident">grey</span> {
        <span class="number">250</span>..<span class="op">=</span><span class="number">255</span> <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="ident">grey</span>,       <span class="ident">grey</span>, <span class="ident">grey</span> )).<span class="number">0</span>,        <span class="comment">//  White</span>
        <span class="number">128</span>..<span class="number">250</span>  <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="ident">grey</span> <span class="op">-</span> <span class="number">100</span>, <span class="ident">grey</span>, <span class="ident">grey</span> <span class="op">-</span> <span class="number">100</span> )).<span class="number">0</span>,  <span class="comment">//  Greenish</span>
        <span class="number">0</span>..<span class="number">128</span>    <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="number">0</span>,          <span class="number">0</span>,    <span class="ident">grey</span> )).<span class="number">0</span>,        <span class="comment">//  Dark Blue</span>
    }
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L494-L510</em></p>
<p><em>Note that <code>128..250</code> means 128 to 249, excluding 250. Whereas <code>250..=255</code> means 250 to 255 (inclusive)</em></p>
<p>The above function <code>convert_color</code> is called by <code>get_color</code> to map CHIP-8 Virtual Pixel Greyscale into 16-bit Physical Pixel Colour when rendering every pixel...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="kw">impl</span> <span class="ident">PixelIterator</span> {
    <span class="doccomment">/// Return the 16-bit colour of the Virtual Pixel</span>
    <span class="kw">fn</span> <span class="ident">get_color</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">u16</span> {
        <span class="comment">//  Get the greyscale colour at Virtual Coordinates (x,y) and convert to 16-bit Physical Colour</span>
        <span class="kw">let</span> <span class="ident">i</span> <span class="op">=</span> <span class="self">self</span>.<span class="ident">x</span> <span class="kw">as</span> <span class="ident">usize</span> <span class="op">+</span> <span class="self">self</span>.<span class="ident">y</span> <span class="kw">as</span> <span class="ident">usize</span> <span class="op">*</span> <span class="ident">SCREEN_WIDTH</span>;
        <span class="kw">let</span> <span class="ident">color</span> <span class="op">=</span> <span class="kw">unsafe</span> { <span class="ident">convert_color</span>( <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] ) };
        <span class="comment">//  Update the greyscale colour at Virtual Coordinates (x,y)</span>
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">y_offset</span> <span class="op">=</span><span class="op">=</span> <span class="number">0</span> {  <span class="comment">//  Update colours only once per Virtual Pixel</span>
            <span class="kw">unsafe</span> { <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] <span class="op">=</span> <span class="ident">update_color</span>( <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] ); }  <span class="comment">//  e.g. fade to black</span>
        }
        <span class="ident">color</span>  <span class="comment">//  Return the 16-bit Physical Colour</span>
    }    
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L376-L385</em></p>
<p>Thus our Initial Loading Screen now looks green. Other Sprites in the game will appear as white (greyscale <code>255</code>) because they are rendered after the game has started seeking button input.</p>
<p>This colouring effect is most obvious in the Space Invaders title screen.</p>
<p><img src="https://lupyuen.github.io/images/chip8-invaders.jpg" alt="Space Invaders title screen in green" /></p>
<p><em>Space Invaders title screen in green</em></p>
<p>What about black pixels? Recall the code from <code>vram_set</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="comment">//  color is true when emulator draws a white pixel, black otherwise</span>
<span class="kw">unsafe</span> { <span class="ident">SCREEN_BUFFER</span>[<span class="ident">i</span>] <span class="op">=</span> 
    <span class="kw">if</span> <span class="ident">color</span> {                          <span class="comment">//  If emulator draws a white pixel...</span>
        ...
    } 
    <span class="kw">else</span> {                              <span class="comment">//  If emulator draws a black pixel...</span>
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">is_interactive</span> { <span class="number">127</span> }  <span class="comment">//  Fade to black</span>
        <span class="kw">else</span> { <span class="number">0</span> }                      <span class="comment">//  Black for initial screen                 </span>
    }  
};</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L169-L198</em></p>
<p>Assuming that the game is actually running (after showing the Initial Loading Screen), <code>is_interactive</code> is flagged as true.</p>
<p>The above code sets the CHIP-8 Virtual Pixel to greyscale <code>127</code>. Which is mapped by <code>convert_color</code> to a dark blue colour...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Convert the Virtual Colour (8-bit greyscale) to 16-bit Physical Pixel Colour</span>
<span class="kw">fn</span> <span class="ident">convert_color</span>(<span class="ident">grey</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">u16</span> {
    <span class="kw">match</span> <span class="ident">grey</span> {
        <span class="number">250</span>..<span class="op">=</span><span class="number">255</span> <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="ident">grey</span>,       <span class="ident">grey</span>, <span class="ident">grey</span> )).<span class="number">0</span>,        <span class="comment">//  White</span>
        <span class="number">128</span>..<span class="number">250</span>  <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="ident">grey</span> <span class="op">-</span> <span class="number">100</span>, <span class="ident">grey</span>, <span class="ident">grey</span> <span class="op">-</span> <span class="number">100</span> )).<span class="number">0</span>,  <span class="comment">//  Greenish</span>
        <span class="number">0</span>..<span class="number">128</span>    <span class="op">=</span><span class="op">&gt;</span> <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="number">0</span>,          <span class="number">0</span>,    <span class="ident">grey</span> )).<span class="number">0</span>,        <span class="comment">//  Dark Blue</span>
    }
}

<span class="doccomment">/// Fade the Virtual Colour (8-bit greyscale) to black</span>
<span class="kw">fn</span> <span class="ident">update_color</span>(<span class="ident">grey</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">u8</span> {
    <span class="kw">match</span> <span class="ident">grey</span> {
        <span class="number">200</span>..<span class="op">=</span><span class="number">255</span> <span class="op">=</span><span class="op">&gt;</span> <span class="ident">grey</span> <span class="op">-</span> <span class="number">2</span>,    <span class="comment">//  Initial white flash fade to normal white</span>
        <span class="number">128</span>..<span class="number">200</span>  <span class="op">=</span><span class="op">&gt;</span> <span class="ident">grey</span>,        <span class="comment">//  Normal white stays the same</span>
        <span class="number">0</span>..<span class="number">128</span>    <span class="op">=</span><span class="op">&gt;</span> <span class="ident">grey</span> <span class="op">&gt;</span><span class="op">&gt;</span> <span class="number">1</span>,   <span class="comment">//  Dark fade to black</span>
    }
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L494-L510</em></p>
<p>Note that the <code>update_color</code> function above is also called by <code>get_color</code> while rendering each pixel of the PineTime display.</p>
<p><code>update_color</code> gradually diminishes greyscale <code>127</code> until it reaches <code>0</code>, at every rendering of the pixel. (<code>&gt;&gt;1</code> shifts the greyscale right by 1 bit, which is the same as dividing by 2)</p>
<p>The result: Black pixels appear as dark blue trails that fade to black.</p>
<p>This colouring effect is most obvious in the Pong game... Watch the trail of the bouncing ball.</p>
<p><img src="https://lupyuen.github.io/images/chip8-pong.jpg" alt="Pong ball trail in blue" /></p>
<p><em>Pong ball trail in blue</em></p>
<h1 id="handle-touch-events" class="section-header"><a href="#handle-touch-events">8 Handle Touch Events</a></h1>
<p>CHIP-8 uses a keypad with 15 keys, marked <code>0</code> to <code>9</code>, <code>A</code> to <code>F</code>.</p>
<p>For PineTime we'll emulate a simple keypad: Tapping the Left part of the touchscreen simulates the key <code>4</code>, Centre part simulates <code>5</code>, Right part simulates <code>6</code>.</p>
<p>This is sufficient for playing Space Invaders, which uses <code>4</code> and <code>6</code> to move your spaceship Left and Right, and <code>5</code> to fire.</p>
<p>Function <code>handle_touch</code> is called by the PineTime Rust touch controller driver with the <code>(x,y)</code> coordinates of the touched point, from <code>(0,0)</code> to <code>(239,239)</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Handle touch events to emulate buttons</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">handle_touch</span>(<span class="ident">x</span>: <span class="ident">u16</span>, <span class="ident">_y</span>: <span class="ident">u16</span>) { 
    <span class="comment">//  We only handle 3 keys: 4, 5, 6, which correspond to Left, Centre, Right</span>
    <span class="kw">let</span> <span class="ident">key</span> <span class="op">=</span>                                                  <span class="comment">//  PHYSICAL_WIDTH is 240</span>
        <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u16</span> <span class="op">/</span> <span class="number">3</span> { <span class="prelude-val">Some</span>(<span class="number">4</span>) }           <span class="comment">//  Left = 4</span>
        <span class="kw">else</span> <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="number">2</span> <span class="op">*</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u16</span> <span class="op">/</span> <span class="number">3</span> { <span class="prelude-val">Some</span>(<span class="number">5</span>) }  <span class="comment">//  Centre = 5</span>
        <span class="kw">else</span> { <span class="prelude-val">Some</span>(<span class="number">6</span>) };                                      <span class="comment">//  Right = 6</span>
    <span class="kw">unsafe</span> { <span class="ident">KEY_PRESSED</span> <span class="op">=</span> <span class="ident">key</span> };
}

<span class="doccomment">/// Represents the key pressed: 0-9 for keys &quot;0&quot; to &quot;9&quot;, 0xa-0xf to keys &quot;A&quot; to &quot;F&quot;, None for nothing pressed</span>
<span class="kw">static</span> <span class="kw-2">mut</span> <span class="ident">KEY_PRESSED</span>: <span class="prelude-ty">Option</span><span class="op">&lt;</span><span class="ident">u8</span><span class="op">&gt;</span> <span class="op">=</span> <span class="prelude-val">None</span>;</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L133-L147</em></p>
<p><code>handle_touch</code> stores the simulated keypress into <code>KEY_PRESSED</code></p>
<p>Note that <code>KEY_PRESSED</code> has type <code>Option&lt;u8&gt;</code>, which is an optional unsigned byte. So <code>KEY_PRESSED</code> may contain a specified byte (like <code>Some(4)</code>), or nothing (<code>None</code>).</p>
<p>The CHIP-8 Emulator checks for keys pressed by calling the <code>key</code> function...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="kw">impl</span> <span class="ident">libchip8</span>::<span class="ident">Hardware</span> <span class="kw">for</span> <span class="ident">Hardware</span> {
    <span class="doccomment">/// Check if the key is pressed.</span>
    <span class="kw">fn</span> <span class="ident">key</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>, <span class="ident">key</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">bool</span> {
        <span class="comment">//  key is 0-9 for keys &quot;0&quot; to &quot;9&quot;, 0xa-0xf to keys &quot;A&quot; to &quot;F&quot;</span>
        <span class="kw">if</span> <span class="op">!</span><span class="self">self</span>.<span class="ident">is_interactive</span> {
            <span class="self">self</span>.<span class="ident">is_interactive</span> <span class="op">=</span> <span class="bool-val">true</span>;
        }
        <span class="self">self</span>.<span class="ident">is_checking_input</span> <span class="op">=</span> <span class="bool-val">true</span>;
        <span class="comment">//  Compare the key with the last touch event</span>
        <span class="kw">if</span> <span class="kw">unsafe</span> { <span class="ident">KEY_PRESSED</span> <span class="op">=</span><span class="op">=</span> <span class="prelude-val">Some</span>(<span class="ident">key</span>) } {
            <span class="kw">unsafe</span> { <span class="ident">KEY_PRESSED</span> <span class="op">=</span> <span class="prelude-val">None</span> };  <span class="comment">//  Clear the touch event</span>
            <span class="kw">return</span> <span class="bool-val">true</span>;
        }
        <span class="bool-val">false</span>
    }</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L492-L504</em></p>
<p>When the Emulator calls <code>key(self, 4)</code>, we should return <code>true</code> if the key <code>4</code> has been pressed, i.e. <code>KEY_PRESSED</code> has value <code>Some(4)</code></p>
<p>This doesn't found efficient for checking many keys... But it's probably OK for retro games.</p>
<p><img src="https://lupyuen.github.io/images/chip8-invaders2.jpg" alt="Playing Space Invaders with 3 touch points: Left, Centre, Right" /></p>
<p><em>Playing Space Invaders with 3 touch points: Left, Centre, Right</em></p>
<p>▶️ <a href="https://youtu.be/tnimq5Rmjgs"><em>Watch the video</em></a></p>
<p>▶️ <a href="https://vt.tiktok.com/jdntsU/"><em>抖音视频</em></a></p>
<h1 id="chip-8-emulator-task" class="section-header"><a href="#chip-8-emulator-task">9 CHIP-8 Emulator Task</a></h1>
<p>The CHIP-8 Emulator blocks and doesn't return when we call its <code>run</code> function. PineTime needs multitasking to refresh the display (via SPI) and to accept touchscreen input, so we need to start a Background Task for the emulator...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Erase the PineTime display and start the CHIP-8 Emulator task</span>
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">on_start</span>() <span class="op">-</span><span class="op">&gt;</span> <span class="ident">MynewtResult</span><span class="op">&lt;</span>()<span class="op">&gt;</span> {
    <span class="comment">//  Create black background</span>
    <span class="kw">let</span> <span class="ident">background</span> <span class="op">=</span> <span class="ident">Rectangle</span>::<span class="op">&lt;</span><span class="ident">Rgb565</span><span class="op">&gt;</span>
        ::<span class="ident">new</span>( <span class="ident">Coord</span>::<span class="ident">new</span>( <span class="number">0</span>, <span class="number">0</span> ), <span class="ident">Coord</span>::<span class="ident">new</span>( <span class="number">239</span>, <span class="number">239</span> ) )   <span class="comment">//  Rectangle coordinates</span>
        .<span class="ident">fill</span>( <span class="prelude-val">Some</span>( <span class="ident">Rgb565</span>::<span class="ident">from</span>(( <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x00</span> )) ) );  <span class="comment">//  Black</span>

    <span class="comment">//  Render background to display</span>
    <span class="ident">druid</span>::<span class="ident">draw_to_display</span>(<span class="ident">background</span>);

    <span class="comment">//  Start the emulator in a background task</span>
    <span class="ident">os</span>::<span class="ident">task_init</span>(                  <span class="comment">//  Create a new task and start it...</span>
        <span class="kw">unsafe</span> { <span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="ident">CHIP8_TASK</span> }, <span class="comment">//  Task object will be saved here</span>
        <span class="kw-2">&amp;</span><span class="macro">init_strn</span><span class="macro">!</span>( <span class="string">&quot;chip8&quot;</span> ),     <span class="comment">//  Name of task</span>
        <span class="prelude-val">Some</span>( <span class="ident">task_func</span> ),    <span class="comment">//  Function to execute when task starts</span>
        <span class="ident">NULL</span>,  <span class="comment">//  Argument to be passed to above function</span>
        <span class="number">20</span>,    <span class="comment">//  Task priority: highest is 0, lowest is 255 (main task is 127), SPI is 10</span>
        <span class="ident">os</span>::<span class="ident">OS_WAIT_FOREVER</span> <span class="kw">as</span> <span class="ident">u32</span>,       <span class="comment">//  Don&#39;t do sanity / watchdog checking</span>
        <span class="kw">unsafe</span> { <span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="ident">CHIP8_TASK_STACK</span> }, <span class="comment">//  Stack space for the task</span>
        <span class="ident">CHIP8_TASK_STACK_SIZE</span> <span class="kw">as</span> <span class="ident">u16</span>      <span class="comment">//  Size of the stack (in 4-byte units)</span>
    ) <span class="question-mark">?</span> ;                                 <span class="comment">//  `?` means check for error</span>

    <span class="comment">//  Return success to the caller</span>
    <span class="prelude-val">Ok</span>(())
}

<span class="doccomment">/// CHIP8 Background Task</span>
<span class="kw">static</span> <span class="kw-2">mut</span> <span class="ident">CHIP8_TASK</span>: <span class="ident">os</span>::<span class="ident">os_task</span> <span class="op">=</span> <span class="macro">fill_zero</span><span class="macro">!</span>(<span class="ident">os</span>::<span class="ident">os_task</span>);

<span class="doccomment">/// Stack space for CHIP8 Task, initialised to 0.</span>
<span class="kw">static</span> <span class="kw-2">mut</span> <span class="ident">CHIP8_TASK_STACK</span>: [<span class="ident">os</span>::<span class="ident">os_stack_t</span>; <span class="ident">CHIP8_TASK_STACK_SIZE</span>] <span class="op">=</span> 
    [<span class="number">0</span>; <span class="ident">CHIP8_TASK_STACK_SIZE</span>];

<span class="doccomment">/// Size of the stack (in 4-byte units). Previously `OS_STACK_ALIGN(256)`  </span>
<span class="kw">const</span> <span class="ident">CHIP8_TASK_STACK_SIZE</span>: <span class="ident">usize</span> <span class="op">=</span> <span class="number">4096</span>;  <span class="comment">//  Must be 4096 and above because CHIP8 Emulator requires substantial stack space</span></pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L39-L66</em></p>
<p>Note that we're using <a href="https://mynewt.apache.org/">Apache Mynewt OS</a> to manage multitasking on PineTime, so we need to use the Mynewt functions (like <code>task_init</code>) for scheduling and synchronising our tasks.</p>
<h1 id="build-and-run-the-emulator" class="section-header"><a href="#build-and-run-the-emulator">10 Build and Run the Emulator</a></h1>
<p>To build the CHIP-8 Emulator for PineTime, edit the Rust configuration file <a href="https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/Cargo.toml"><code>rust/app/Cargo.toml</code></a>.</p>
<p>Uncomment the feature <code>chip8_app</code> and comment out all other features, like this...</p>
<pre><code class="language-yaml">[features]
default =  [          # Select the conditional compiled features
    # &quot;display_app&quot;,  # Disable graphics display app
    # &quot;ui_app&quot;,       # Disable druid UI app
    # &quot;visual_app&quot;,   # Disable Visual Rust app
    &quot;chip8_app&quot;,      # Enable CHIP8 Emulator app
    # &quot;chip8_curve&quot;,  # Uncomment to render CHIP8 Emulator as curved surface (requires chip8_app)
    # &quot;use_float&quot;,    # Disable floating-point support e.g. GPS geolocation
]
</code></pre>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/Cargo.toml</em></p>
<p>If you're using the curved rendering feature (explained in the next section), uncomment both <code>chip8_app</code> and <code>chip8_curve</code>.</p>
<p>Edit the Mynewt configuration file <a href="https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/apps/my_sensor_app/syscfg.yml"><code>apps/my_sensor_app/syscfg.yml</code></a>.</p>
<p>Set <code>OS_MAIN_STACK_SIZE</code> to <code>2048</code>...</p>
<pre><code class="language-yaml">syscfg.vals:
    # OS_MAIN_STACK_SIZE: 1024  #  Small stack size: 4 KB
    OS_MAIN_STACK_SIZE: 2048    #  Normal stack size: 8 KB
    # OS_MAIN_STACK_SIZE: 4096  #  Large stack size: 16 KB
</code></pre>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/apps/my_sensor_app/syscfg.yml</em></p>
<p>This shrinks the default system stack size and allows us to allocate a larger stack that's needed for the emulator task.</p>
<p>Then build the PineTime firmware according to <a href="https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/README.md">the instructions here</a>.</p>
<h1 id="distort-the-chip-8-rendering" class="section-header"><a href="#distort-the-chip-8-rendering">11 Distort the CHIP-8 Rendering</a></h1>
<p>Remember we said earlier that every Virtual Pixel on CHIP-8 is rendered as a rectangular chunk of 15 Physical Pixels on the PineTime display. (Because we need to stretch the pixel to fill the PineTime display)</p>
<p><em>Not super efficient... But since we're blasting out 15 pixels anyway... Can we be more creative? What if we blast the 15 pixels in a curvy way like this...</em></p>
<p><img src="https://lupyuen.github.io/images/chip8-blinky-curve.jpg" alt="Blinky distorted on a curved surface" /></p>
<p><em>Blinky distorted on a curved surface</em></p>
<p>This looks interesting... Almost &quot;Organic&quot;, like a CRT display protruding from the PineTime screen.  So let's get adventurous! </p>
<p><em>How shall we distort the CHIP-8 Emulator in a curvy way based on this rectangular grid...</em></p>
<p><img src="https://lupyuen.github.io/images/chip8-blinky.jpg" alt="Blinky without distortion" /></p>
<p><em>Blinky without distortion... See how the distorted version fills the entire display width nicely? That's the power of distortion!</em></p>
<p>Mathematically we are mapping a <strong>Square (the CHIP-8 output) to a Sphere (the curved rendering surface)</strong> like this...</p>
<p><img src="https://lupyuen.github.io/images/chip8-curve.jpg" alt="Distorting CHIP-8 on a curved surface" /></p>
<p><em>From https://stackoverflow.com/questions/18264703/mapping-a-2d-grid-onto-a-sphere</em></p>
<p>The obvious approach would be to crack open the Sine and Cosine Functions from our high school textbooks and work out the correct formula... But we shall do no such boring things here!</p>
<p>Let's look at <strong>3D Interpolation</strong> instead.  I have a strong hunch that...</p>
<ol>
<li>
<p>Calling <code>sin</code> and <code>cos</code> at every pixel rendering would be too taxing on our nRF52 microcontroller</p>
</li>
<li>
<p>We have plenty of ROM on PineTime (512 KB). Perfect for our nRF52 microcontroller to look up simple <strong>Lookup Tables</strong> that will map Virtual Pixel to Physical Pixels and vice versa.</p>
</li>
<li>
<p>How will the Lookup Tables work? Given a Virtual Pixel on CHIP-8, we need to find out the corresponding Physical Pixels on the curvy PineTime display. </p>
</li>
<li>
<p>And the Lookup Tables will also tell us the Virtual Pixel that corresponds to each Physical Pixel. (This is a One Virtual Pixel to Multiple Physical Pixel mapping... Mathematically, <code>1:N</code>)</p>
</li>
<li>
<p>The PineTime display is only 240x240... Precomputing and storing the Lookup Tables into ROM should be easy. Note that the mapping of Square to Sphere is Symmetric on the X and Y axes. So we only need to compute one quadrant of the mapping! (I chose the lower right quadrant)</p>
</li>
<li>
<p>To compute the Lookup Tables, we'll take a few points from the Square to Sphere mapping and interpolate them (i.e. run a Rust program to fill in the missing pixels between the points).</p>
</li>
<li>
<p>Interpolating the pixels is probably a good idea for the long term... It lets us tweak the mapping manually by shifting the points. (Instead of figuring out complicated math formulae)</p>
</li>
</ol>
<h1 id="interpolate-the-sphere-to-square-mapping-physical--virtual" class="section-header"><a href="#interpolate-the-sphere-to-square-mapping-physical--virtual">12 Interpolate the Sphere to Square mapping (Physical → Virtual)</a></h1>
<p><em>How shall we get the Interpolation Points to map the rectangular Virtual Pixels on CHIP-8 to the curved Physical Pixels on PineTime display? Let's copy them literally from the <a href="https://stackoverflow.com/questions/18264703/mapping-a-2d-grid-onto-a-sphere_">Square to Sphere Interpolation Diagram</a>!</em></p>
<p><img src="https://lupyuen.github.io/images/chip8-points.png" alt="Interpolation Points copied from diagram into spreadsheet" /></p>
<p><em>Interpolation Points copied from diagram into spreadsheet. From https://docs.google.com/spreadsheets/d/1G9kLS0Es6kwcMA3SC50w5-T-LBYi3NQeY98y7HOAovs/edit#gid=0</em></p>
<p>This produces 49 curved Intepolation Points (7 * 7) that we shall map into a rectangular grid for CHIP-8. As shown in the top left corner of <a href="https://docs.google.com/spreadsheets/d/1G9kLS0Es6kwcMA3SC50w5-T-LBYi3NQeY98y7HOAovs/edit#gid=0">the spreadsheet</a>...</p>
<ul>
<li>
<p><code>(586, 105)</code> on the curved PineTime surface, normalised to <code>(-99, -84)</code>, maps to normalised <code>(-32, -16)</code> on CHIP-8</p>
</li>
<li>
<p><code>(618, 95)</code> on the curved PineTime surface, normalised to <code>(-65, -93)</code>, maps to normalised <code>(-21, -16)</code> on CHIP-8</p>
</li>
<li>
<p><code>(574, 137)</code> on the curved PineTime surface, normalised to <code>(-112, -56)</code>, maps to normalised <code>(-32, -11)</code> on CHIP-8</p>
</li>
</ul>
<p><em>We normalise Physical PineTime Pixels on the curved surface to fall within (-120, -100) to (120, 100)... And Virtual CHIP-8 Pixels within (-32, -16) to (32, 16)</em></p>
<p>But since X and Y are Symmetric in the mapping, we'll consider only the lower right quadrant of the spreadsheet...</p>
<ul>
<li>
<p>Normalised <code>(1, 0)</code> on the curved PineTime surface maps to normalised <code>(0, 0)</code> on CHIP-8</p>
</li>
<li>
<p>Normalised <code>(45, 0)</code> on the curved PineTime surface maps to normalised <code>(11, 0)</code> on CHIP-8</p>
</li>
<li>
<p>Normalised <code>(1, 36)</code> on the curved PineTime surface maps to normalised <code>(0, 5)</code> on CHIP-8</p>
</li>
</ul>
<p>We're now ready to interpolate the missing Physical and Virtual Pixels. We'll feed the above numbers to the <strong><a href="%5Bhttps://crates.io/crates/spade%5D">[spade] crate</a></strong> for interpolation, in two steps.</p>
<p>We'll feed the numbers for PineTime Physical (X, Y) → CHIP-8 Virtual X first...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;interpolate_x&quot;</span>)]</span>  <span class="comment">//  If interpolating X values...</span>
<span class="kw">fn</span> <span class="ident">load_data</span>() <span class="op">-</span><span class="op">&gt;</span> [<span class="ident">Point3</span><span class="op">&lt;</span><span class="ident">f64</span><span class="op">&gt;</span>; <span class="number">16</span>] {
    [
        <span class="comment">//  Generated by https://docs.google.com/spreadsheets/d/1G9kLS0Es6kwcMA3SC50w5-T-LBYi3NQeY98y7HOAovs/edit#gid=1875321785</span>
        <span class="ident">p</span>( <span class="number">1</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical ( 1,  0) → Virtual X =  0</span>
        <span class="ident">p</span>(<span class="number">45</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">11</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical (45,  0) → Virtual X = 11</span>
        <span class="ident">p</span>( <span class="number">1</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">36</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical ( 1, 36) → Virtual X =  0</span>
        ...</pre></div>
<p><em>From https://github.com/lupyuen/interpolate-surface/blob/master/src/delaunay_creation.rs</em></p>
<p>Then PineTime Physical (X, Y) → CHIP-8 Virtual Y...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;interpolate_y&quot;</span>)]</span>  <span class="comment">//  If interpolating Y values...</span>
<span class="kw">fn</span> <span class="ident">load_data</span>() <span class="op">-</span><span class="op">&gt;</span> [<span class="ident">Point3</span><span class="op">&lt;</span><span class="ident">f64</span><span class="op">&gt;</span>; <span class="number">16</span>] {
    [
        <span class="comment">//  Generated by https://docs.google.com/spreadsheets/d/1G9kLS0Es6kwcMA3SC50w5-T-LBYi3NQeY98y7HOAovs/edit#gid=1875321785</span>
        <span class="ident">p</span>( <span class="number">1</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical ( 1,  0) → Virtual Y = 0</span>
        <span class="ident">p</span>(<span class="number">45</span> <span class="kw">as</span> <span class="ident">f64</span>,  <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">0</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical (45,  0) → Virtual Y = 0</span>
        <span class="ident">p</span>( <span class="number">1</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">36</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="number">5</span> <span class="kw">as</span> <span class="ident">f64</span>),  <span class="comment">//  Physical ( 1, 36) → Virtual Y = 5</span>
        ...</pre></div>
<p><em>From https://github.com/lupyuen/interpolate-surface/blob/master/src/delaunay_creation.rs</em></p>
<p>We'll use <strong><a href="https://stoeoef.gitbooks.io/spade-user-manual/content/interpolation.html">Natural Neighbor 3D Interpolation</a></strong> from the [spade] crate to interpolate the missing Virtual (X, Y) pixels, in two steps (Virtual X then Virtual Y).</p>
<p><em>Why is this considered 3D Interpolation, not 2D Interpolation? Because we are mapping two numbers (Physical X = 1, Physical Y = 0) to a third number (Virtual X = 0)</em></p>
<p>It sounds like a miracle, but the <a href="https://github.com/lupyuen/interpolate-surface">[spade] 3D Interpolation Program</a> will produce smoothly-interpolated CHIP-8 Virtual X values for <strong>every single PineTime pixel of Physical (X, Y)</strong> from (0, 0) to (120, 100)... (That's the lower right quadrant)</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="ident">Physical</span> ( <span class="number">1</span>,  <span class="number">0</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">0</span>  <span class="comment">//  Provided value</span>
<span class="ident">Physical</span> ( <span class="number">2</span>,  <span class="number">0</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">0</span>  <span class="comment">//  Interpolated value</span>
<span class="ident">Physical</span> ( <span class="number">3</span>,  <span class="number">0</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">1</span>  <span class="comment">//  Interpolated value</span>
...
<span class="ident">Physical</span> (<span class="number">45</span>,  <span class="number">1</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span> <span class="number">11</span>  <span class="comment">//  Provided value</span>
<span class="ident">Physical</span> (<span class="number">46</span>,  <span class="number">1</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span> <span class="number">11</span>  <span class="comment">//  Interpolated value</span>
<span class="ident">Physical</span> (<span class="number">47</span>,  <span class="number">1</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span> <span class="number">11</span>  <span class="comment">//  Interpolated value</span>
...
<span class="ident">Physical</span> ( <span class="number">1</span>, <span class="number">36</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">0</span>  <span class="comment">//  Provided value</span>
<span class="ident">Physical</span> ( <span class="number">2</span>, <span class="number">36</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">0</span>  <span class="comment">//  Interpolated value</span>
<span class="ident">Physical</span> ( <span class="number">3</span>, <span class="number">36</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="ident">Virtual</span> <span class="ident">X</span> <span class="op">=</span>  <span class="number">1</span>  <span class="comment">//  Interpolated value</span>
...</pre></div>
<p><em>Output of <a href="https://github.com/lupyuen/interpolate-surface">interpolate-surface program</a>. From https://docs.google.com/spreadsheets/d/1G9kLS0Es6kwcMA3SC50w5-T-LBYi3NQeY98y7HOAovs/edit#gid=1436721555</em></p>
<p>The program will also produce smoothly-interpolated CHIP-8 Virtual Y values for every single PineTime pixel of Physical (X, Y) in the lower right quadrant. (Need to edit <code>Cargo.toml</code> and select the feature <code>interpolate_y</code>)</p>
<p>So mapping from every Physical Pixel on the curved PineTime display to Virtual Pixel on CHIP-8 is complete!</p>
<h1 id="interpolate-the-square-to-sphere-mapping-virtual--physical" class="section-header"><a href="#interpolate-the-square-to-sphere-mapping-virtual--physical">13 Interpolate the Square to Sphere mapping (Virtual → Physical)</a></h1>
<p>Now let's map every Virtual Pixel on CHIP-8 to Physical Pixels on the curved PineTime display.</p>
<p>Fortunately there's no need to use 3D Interpolation here... We'll simply search for all Physical Pixels that correspond to each Virtual Pixel, using the Physical → Virtual mapping that we have created earlier.</p>
<p><em>(Remember that Physical Pixels range from (0, 0) to (120, 100), so the search should be quite fast on a desktop computer)</em></p>
<p>Here's how we find all Physical Pixels that are mapped from each Virtual Pixel...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Given a grid of Physical (x,y) Coordinates and their interpolated Virtual (x,y) Coordinates, </span>
<span class="doccomment">/// find all Physical (x,y) Coordinates that interpolate to (x_virtual,y_virtual).</span>
<span class="doccomment">/// Return the (left, top, right, bottom) of the Bounding Box that encloses these found points.</span>
<span class="doccomment">/// x_virtual and y_virtual are truncated to integer during comparison.</span>
<span class="doccomment">/// Function returns `None` if (x_virtual,y_virtual) was not found.</span>
<span class="kw">fn</span> <span class="ident">get_bounding_box</span>(
    <span class="ident">x_virtual_grid</span>: <span class="kw-2">&amp;</span>[[<span class="ident">f64</span>; <span class="ident">X_PHYSICAL_SUBDIVISIONS</span> <span class="op">+</span> <span class="number">1</span>]; <span class="ident">Y_PHYSICAL_SUBDIVISIONS</span> <span class="op">+</span> <span class="number">1</span>],
    <span class="ident">y_virtual_grid</span>: <span class="kw-2">&amp;</span>[[<span class="ident">f64</span>; <span class="ident">X_PHYSICAL_SUBDIVISIONS</span> <span class="op">+</span> <span class="number">1</span>]; <span class="ident">Y_PHYSICAL_SUBDIVISIONS</span> <span class="op">+</span> <span class="number">1</span>],
    <span class="ident">x_virtual</span>: <span class="ident">f64</span>,
    <span class="ident">y_virtual</span>: <span class="ident">f64</span>
) <span class="op">-</span><span class="op">&gt;</span> <span class="prelude-ty">Option</span><span class="op">&lt;</span>(<span class="ident">f64</span>, <span class="ident">f64</span>, <span class="ident">f64</span>, <span class="ident">f64</span>)<span class="op">&gt;</span> {
    <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">left</span>: <span class="ident">f64</span> <span class="op">=</span> <span class="ident">f64</span>::<span class="ident">MAX</span>;
    <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">top</span>: <span class="ident">f64</span> <span class="op">=</span> <span class="ident">f64</span>::<span class="ident">MAX</span>;
    <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">right</span>: <span class="ident">f64</span> <span class="op">=</span> <span class="ident">f64</span>::<span class="ident">MIN</span>;
    <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">bottom</span>: <span class="ident">f64</span> <span class="op">=</span> <span class="ident">f64</span>::<span class="ident">MIN</span>;
    <span class="comment">//  For all Physical (x,y) Coordinates...</span>
    <span class="kw">for</span> <span class="ident">y</span> <span class="kw">in</span> <span class="number">0</span>..<span class="op">=</span><span class="ident">Y_PHYSICAL_SUBDIVISIONS</span> {
        <span class="kw">for</span> <span class="ident">x</span> <span class="kw">in</span> <span class="number">0</span>..<span class="op">=</span><span class="ident">X_PHYSICAL_SUBDIVISIONS</span> {
            <span class="comment">//  Get the Physical (x,y) Coordinates</span>
            <span class="kw">let</span> <span class="ident">pos</span> <span class="op">=</span> <span class="ident">transform_physical_point</span>(<span class="ident">cg</span>::<span class="ident">Point2</span>::<span class="ident">new</span>(<span class="ident">x</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="ident">y</span> <span class="kw">as</span> <span class="ident">f64</span>));

            <span class="comment">//  Get the interpolated Virtual (x,y) Coordinates</span>
            <span class="kw">let</span> <span class="ident">x_interpolated</span> <span class="op">=</span> <span class="ident">x_virtual_grid</span>[<span class="ident">y</span>][<span class="ident">x</span>].<span class="ident">floor</span>();
            <span class="kw">let</span> <span class="ident">y_interpolated</span> <span class="op">=</span> <span class="ident">y_virtual_grid</span>[<span class="ident">y</span>][<span class="ident">x</span>].<span class="ident">floor</span>();

            <span class="comment">//  Skip if not matching</span>
            <span class="kw">if</span> <span class="ident">x_interpolated</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">!</span><span class="op">=</span> <span class="ident">x_virtual</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">|</span><span class="op">|</span> 
                <span class="ident">y_interpolated</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">!</span><span class="op">=</span> <span class="ident">y_virtual</span> <span class="kw">as</span> <span class="ident">u8</span> { <span class="kw">continue</span>; }

            <span class="comment">//  Find the Bounding Box of the Physical (x,y) Coordinates</span>
            <span class="kw">if</span> <span class="ident">pos</span>.<span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">left</span>   { <span class="ident">left</span>   <span class="op">=</span> <span class="ident">pos</span>.<span class="ident">x</span>; }
            <span class="kw">if</span> <span class="ident">pos</span>.<span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">top</span>    { <span class="ident">top</span>    <span class="op">=</span> <span class="ident">pos</span>.<span class="ident">y</span>; }
            <span class="kw">if</span> <span class="ident">pos</span>.<span class="ident">x</span> <span class="op">&gt;</span> <span class="ident">right</span>  { <span class="ident">right</span>  <span class="op">=</span> <span class="ident">pos</span>.<span class="ident">x</span>; }
            <span class="kw">if</span> <span class="ident">pos</span>.<span class="ident">y</span> <span class="op">&gt;</span> <span class="ident">bottom</span> { <span class="ident">bottom</span> <span class="op">=</span> <span class="ident">pos</span>.<span class="ident">y</span>; }
        }
    };
    <span class="kw">if</span> <span class="ident">left</span> <span class="op">&lt;</span> <span class="ident">f64</span>::<span class="ident">MAX</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">top</span> <span class="op">&lt;</span> <span class="ident">f64</span>::<span class="ident">MAX</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span>
        <span class="ident">right</span> <span class="op">&gt;</span> <span class="ident">f64</span>::<span class="ident">MIN</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">bottom</span> <span class="op">&gt;</span> <span class="ident">f64</span>::<span class="ident">MIN</span> {  <span class="comment">//  (x_virtual,y_virtual) found</span>
            <span class="prelude-val">Some</span>((<span class="ident">left</span>.<span class="ident">floor</span>(), <span class="ident">top</span>.<span class="ident">floor</span>(), <span class="ident">right</span>.<span class="ident">floor</span>(), <span class="ident">bottom</span>.<span class="ident">floor</span>())) 
    } <span class="kw">else</span> { <span class="prelude-val">None</span> }  <span class="comment">//  (x_virtual,y_virtual) not found</span>
}</pre></div>
<p><em>From https://github.com/lupyuen/interpolate-surface/blob/master/src/main.rs#L297-L337</em></p>
<p>Each CHIP-8 Virtual Pixel may map to one or more PineTime Physical Pixels. Instead of returning all matching Physical Pixels, we return the Physical Bounding Box instead... The Physical Bounding Box at Physical Coordinates (Left, Top) to (Right, Bottom) is the smallest box that contains all the matching Physical Pixels.</p>
<p>The above function <code>get_bounding_box</code> is called like this for every Virtual CHIP-8 Pixel...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For all Virtual (x,y) Coordinates, compute the Bounding Box that encloses the corresponding Physical (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Physical Pixels to redraw when a Virtual Pixel is updated.</span>
<span class="kw">fn</span> <span class="ident">generate_virtual_to_physical_map</span>() {
    <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;VIRTUAL_TO_PHYSICAL_MAP=&quot;</span>);
    <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;[&quot;</span>);
    <span class="kw">for</span> <span class="ident">y</span> <span class="kw">in</span> <span class="number">0</span>..<span class="ident">Y_VIRTUAL_SUBDIVISIONS</span> {
        <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;[&quot;</span>);
        <span class="kw">for</span> <span class="ident">x</span> <span class="kw">in</span> <span class="number">0</span>..<span class="ident">X_VIRTUAL_SUBDIVISIONS</span> {
            <span class="comment">//  Convert the normalised (x,y) into Virtual (x,y) Coordinates</span>
            <span class="kw">let</span> <span class="ident">pos</span> <span class="op">=</span> <span class="ident">transform_virtual_point</span>(<span class="ident">cg</span>::<span class="ident">Point2</span>::<span class="ident">new</span>(<span class="ident">x</span> <span class="kw">as</span> <span class="ident">f64</span>, <span class="ident">y</span> <span class="kw">as</span> <span class="ident">f64</span>));
            <span class="comment">//  For all Physical (x,y) that interpolate to the Virtual (x,y), find the bounding box</span>
            <span class="kw">let</span> <span class="ident">bounding_box</span> <span class="op">=</span> <span class="ident">get_bounding_box</span>(
                <span class="ident">data</span>::<span class="ident">X_VIRTUAL_GRID</span>,
                <span class="ident">data</span>::<span class="ident">Y_VIRTUAL_GRID</span>,
                <span class="ident">pos</span>.<span class="ident">x</span>,
                <span class="ident">pos</span>.<span class="ident">y</span>);  <span class="comment">//  Returns (left, top, right, bottom) for the Bounding Box</span>
            <span class="kw">if</span> <span class="kw">let</span> <span class="prelude-val">Some</span>((<span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span>)) <span class="op">=</span> <span class="ident">bounding_box</span> {
                <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;({:.0},{:.0},{:.0},{:.0}),&quot;</span>, <span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span>);
                <span class="comment">/* if left as u8 == right as u8 &amp;&amp; top as u8 == bottom as u8 {
                    print!(&quot;****&quot;);  //  Flag out Virtual Points that map to a single Physical Point
                } */</span>
            } <span class="kw">else</span> {
                <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;(255,255,255,255),&quot;</span>);
            }
            <span class="comment">//  println!(&quot;XVirtual={:.0}, YVirtual={:.0}, BoundBox={:.?}&quot;, pos.x, pos.y, bounding_box);</span>
        }
        <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;],&quot;</span>);
    }
    <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;]\n&quot;</span>);    
}</pre></div>
<p><em>From https://github.com/lupyuen/interpolate-surface/blob/master/src/main.rs#L266-L296</em></p>
<p>Thus the above function <code>generate_virtual_to_physical_map</code> computes the mapping: CHIP-8 Virtual Pixel → PineTime Physical Bounding Box. And produces the <code>VIRTUAL_TO_PHYSICAL_MAP</code> Lookup Table.</p>
<p>The other Lookup Table <code>PHYSICAL_TO_VIRTUAL_MAP</code> is produced by the function <code>generate_physical_to_virtual_map</code>...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For all Physical (x,y) Coordinates, return the corresponding Virtual (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Virtual Pixel to fetch the colour value when rendering a Physical Pixel.</span>
<span class="kw">fn</span> <span class="ident">generate_physical_to_virtual_map</span>() {
    <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;PHYSICAL_TO_VIRTUAL_MAP=&quot;</span>);
    <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;[&quot;</span>);
    <span class="kw">for</span> <span class="ident">y</span> <span class="kw">in</span> <span class="number">0</span>..<span class="ident">Y_PHYSICAL_SUBDIVISIONS</span> {
        <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;[&quot;</span>);
        <span class="kw">for</span> <span class="ident">x</span> <span class="kw">in</span> <span class="number">0</span>..<span class="ident">X_PHYSICAL_SUBDIVISIONS</span> {
            <span class="comment">//  Convert the normalised (x,y) into Physical (x,y) Coordinates</span>
            <span class="comment">//  let physical_point = transform_physical_point(cg::Point2::new(x as f64, y as f64));</span>
            <span class="comment">//  Construct the interpolated Virtual (x,y) Coordinates</span>
            <span class="kw">let</span> <span class="ident">virtual_point</span> <span class="op">=</span> <span class="ident">cg</span>::<span class="ident">Point2</span>::<span class="ident">new</span>(
                <span class="ident">data</span>::<span class="ident">X_VIRTUAL_GRID</span>[<span class="ident">y</span>][<span class="ident">x</span>] <span class="kw">as</span> <span class="ident">f64</span>, 
                <span class="ident">data</span>::<span class="ident">Y_VIRTUAL_GRID</span>[<span class="ident">y</span>][<span class="ident">x</span>] <span class="kw">as</span> <span class="ident">f64</span>
            );
            <span class="macro">print</span><span class="macro">!</span>(<span class="string">&quot;({:.0},{:.0}),&quot;</span>, <span class="ident">virtual_point</span>.<span class="ident">x</span>, <span class="ident">virtual_point</span>.<span class="ident">y</span>);
        }
        <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;],&quot;</span>);
    }
    <span class="macro">println</span><span class="macro">!</span>(<span class="string">&quot;]\n&quot;</span>);    
}</pre></div>
<p><em>From https://github.com/lupyuen/interpolate-surface/blob/master/src/main.rs#L244-L265</em></p>
<p>We'll peek at the contents of Lookup Tables <code>PHYSICAL_TO_VIRTUAL_MAP</code> and <code>VIRTUAL_TO_PHYSICAL_MAP</code> in a while.</p>
<h1 id="validate-the-square-to-sphere-interpolation" class="section-header"><a href="#validate-the-square-to-sphere-interpolation">14 Validate the Square to Sphere Interpolation</a></h1>
<p><em>Why did we choose Natural Neighbor Interpolation? (Even though I'm no expert at 3D Interpolation?)</em></p>
<p>Amazingly, the [spade] crate includes an awesome feature to visualise the 3D Interpolated points... in 3D! Run <a href="https://github.com/lupyuen/interpolate-surface"><code>interpolate_surface</code></a>, press and hold the Left Mouse Button to rotate the 3D view, press and hold the right Mouse Button to move the 3D view.</p>
<p>▶️ <a href="https://youtu.be/IpXfkvenx3k"><em>Watch the video</em></a></p>
<p>▶️ <a href="https://vt.tiktok.com/2WTTLL/"><em>抖音视频</em></a></p>
<p>The <code>interpolate_surface</code> program includes <a href="https://stoeoef.gitbooks.io/spade-user-manual/content/interpolation.html">multiple 3D Interpolation methods</a>. Press <code>G</code> to switch 3D Interpolation methods.  Let's compare the first one that appears (Barycentic Interpolation at right) with the second one (Natural Neighbor Interpolation at left)...</p>
<p><img src="https://lupyuen.github.io/images/chip8-interpolate.png" alt="Natural Neighbor Interpolation (left) vs Barycentic Interpolation (right) in 3D perspective" /></p>
<p><em>Natural Neighbor Interpolation (left) vs Barycentic Interpolation (right) in 3D perspective. Zoom in to see details. From https://github.com/lupyuen/interpolate-surface</em></p>
<p>Recall that we're using (X, Y) coordinates to interpolate a Z value. Which is shown above as the height of each point. (Like hilltops)</p>
<p>Barycentic Interpolation (right) has an unusual kink, so it doesn't look like a smooth interpolation.</p>
<p>Thus we picked Natural Neighbor Interpolation (left), which looks much smoother. This lets us verify visually that our pixels are indeed interpolated smoothly between the given points.</p>
<p>The smooth result of the 3D Interpolation is obvious... Straight lines are gently curved on the PineTime display.  All this interpolated from only <strong>16 points</strong> in the lower right quadrant!</p>
<p><img src="https://lupyuen.github.io/images/chip8-blinky-curve.jpg" alt="Blinky distorted on a curved surface, interpolated from 16 points in the lower right quadrant" /></p>
<p><em>Blinky distorted on a curved surface, interpolated from 16 points in the lower right quadrant</em></p>
<h1 id="map-physical-pixels-to-virtualpixels" class="section-header"><a href="#map-physical-pixels-to-virtualpixels">15 Map Physical Pixels to Virtual Pixels</a></h1>
<p>Now that we have precomputed the Lookup Tables <code>PHYSICAL_TO_VIRTUAL_MAP</code> and <code>VIRTUAL_TO_PHYSICAL_MAP</code>, let's embed them into PineTime's Flash ROM.</p>
<p>Here's how we embed <code>PHYSICAL_TO_VIRTUAL_MAP</code>, the array that maps every Physical Pixel to the corresponding Virtual Pixel...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For Physical (x,y) Coordinates, return the corresponding Virtual (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Virtual Pixel to fetch the colour value when rendering a Physical Pixel.</span>
<span class="doccomment">/// (x,y) must belong to the X &gt;= 0, Y &gt;= 0 quadrant</span>
<span class="kw">fn</span> <span class="ident">map_physical_to_virtual_normalised</span>(<span class="ident">x</span>: <span class="ident">u8</span>, <span class="ident">y</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> (<span class="ident">u8</span>, <span class="ident">u8</span>) {
    <span class="kw">let</span> <span class="ident">x_index</span> <span class="op">=</span> <span class="ident">x</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_TO_VIRTUAL_MAP_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="kw">let</span> <span class="ident">y_index</span> <span class="op">=</span> <span class="ident">y</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_TO_VIRTUAL_MAP_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="kw">let</span> <span class="ident">virtual_pixel</span> <span class="op">=</span> <span class="ident">PHYSICAL_TO_VIRTUAL_MAP</span>[<span class="ident">y_index</span> <span class="kw">as</span> <span class="ident">usize</span>][<span class="ident">x_index</span> <span class="kw">as</span> <span class="ident">usize</span>];  <span class="comment">//  Returns (x,y)</span>
    <span class="ident">virtual_pixel</span>
}

<span class="doccomment">/// For each Physical (x,y) Coordinate, return the corresponding Virtual (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Virtual Pixel to fetch the colour value when rendering a Physical Pixel.</span>
<span class="doccomment">/// Since X and Y are symmetric, this grid only covers one quadrant (X &gt;= 0, Y &gt;= 0)</span>
<span class="kw">static</span> <span class="ident">PHYSICAL_TO_VIRTUAL_MAP</span>: <span class="kw-2">&amp;</span>[[(<span class="ident">u8</span>,<span class="ident">u8</span>); <span class="ident">PHYSICAL_TO_VIRTUAL_MAP_WIDTH</span>]; <span class="ident">PHYSICAL_TO_VIRTUAL_MAP_HEIGHT</span> <span class="op">=</span> <span class="op">&amp;</span>  <span class="comment">//  Row=Y, Col=X</span>
[               <span class="comment">//  Copied from output of https://github.com/lupyuen/interpolate-surface</span>
    [           <span class="comment">//  Physical Row 0</span>
        (<span class="number">0</span>,<span class="number">0</span>),  <span class="comment">//  Physical Row 0, Col 0   =&gt; Virtual Col 0,  Row 0</span>
        (<span class="number">0</span>,<span class="number">0</span>),  <span class="comment">//  Physical Row 0, Col 1   =&gt; Virtual Col 0,  Row 0</span>
        ...
        (<span class="number">32</span>,<span class="number">0</span>), <span class="comment">//  Physical Row 0, Col 239 =&gt; Virtual Col 32, Row 0</span>
    ],
    [           <span class="comment">//  Physical Row 1</span>
        (<span class="number">0</span>,<span class="number">0</span>),  <span class="comment">//  Physical Row 1, Col 0   =&gt; Virtual Col 0,  Row 0</span>
        (<span class="number">0</span>,<span class="number">0</span>),  <span class="comment">//  Physical Row 1, Col 1   =&gt; Virtual Col 0,  Row 0</span>
        ...
        (<span class="number">32</span>,<span class="number">0</span>), <span class="comment">//  Physical Row 1, Col 239 =&gt; Virtual Col 32, Row 0</span>
    ],
...</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L645-L673</em></p>
<p><code>PHYSICAL_TO_VIRTUAL_MAP</code> is the Lookup Table precomputed by the <code>generate_physical_to_virtual_map</code> function that we have seen earlier. This is a 2D array with 100 rows and 120 columns, covering the Physical Pixels in the lower right quadrant of the PineTime display.</p>
<p>Each element of <code>PHYSICAL_TO_VIRTUAL_MAP</code> (indexed by Physical Row and Physical Column) is a tuple (Virtual X, Virtual Y), the coordinates of the mapped Virtual Pixel on CHIP-8.</p>
<p><code>map_physical_to_virtual_normalised</code> is the function used to look up the <code>PHYSICAL_TO_VIRTUAL_MAP</code> table by Normalised Physical (X, Y) Coordinates.</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For Physical (x,y) Coordinates, return the corresponding Virtual (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Virtual Pixel to fetch the colour value when rendering a Physical Pixel.</span>
<span class="kw">fn</span> <span class="ident">map_physical_to_virtual</span>(<span class="ident">x</span>: <span class="ident">u8</span>, <span class="ident">y</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> (<span class="ident">u8</span>, <span class="ident">u8</span>) {
    <span class="comment">//  Check which quadrant (x,y) belongs to and flip accordingly</span>
    <span class="kw">let</span> <span class="ident">flip</span> <span class="op">=</span>  <span class="comment">//  (flip for X, flip for Y)</span>
        <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">true</span>, <span class="bool-val">true</span>)  <span class="comment">//  Top left quadrant: Flip horizontally and vertically</span>
        } <span class="kw">else</span> <span class="kw">if</span> <span class="ident">x</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">false</span>, <span class="bool-val">true</span>)   <span class="comment">//  Top right quadrant: Flip vertically</span>
        } <span class="kw">else</span> <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">true</span>, <span class="bool-val">false</span>)   <span class="comment">//  Bottom left quadrant: Flip horizontally</span>
        } <span class="kw">else</span> {
            (<span class="bool-val">false</span>, <span class="bool-val">false</span>)    <span class="comment">//  Bottom right quadrant: Don&#39;t flip</span>
        };
    <span class="kw">let</span> <span class="ident">x_normalised</span> <span class="op">=</span> 
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">0</span> { <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">x</span> } 
        <span class="kw">else</span>      { <span class="ident">x</span> <span class="op">-</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> };
    <span class="kw">let</span> <span class="ident">y_normalised</span> <span class="op">=</span> 
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">1</span> { <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">y</span> }
        <span class="kw">else</span>      { <span class="ident">y</span> <span class="op">-</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> };
    <span class="kw">let</span> <span class="ident">p</span> <span class="op">=</span> <span class="ident">map_physical_to_virtual_normalised</span>(<span class="ident">x_normalised</span>, <span class="ident">y_normalised</span>);  <span class="comment">//  Returns (x,y)</span>
    <span class="kw">let</span> <span class="ident">p2</span> <span class="op">=</span> (
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">0</span> { <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">p</span>.<span class="number">0</span> } 
        <span class="kw">else</span>      { <span class="ident">p</span>.<span class="number">0</span> <span class="op">+</span> <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
        ,
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">1</span> { <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">p</span>.<span class="number">1</span> } 
        <span class="kw">else</span>      { <span class="ident">p</span>.<span class="number">1</span> <span class="op">+</span> <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
    );
    <span class="comment">//  Crop to screen size</span>
    (
        <span class="ident">p2</span>.<span class="number">0</span>.<span class="ident">min</span>(<span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),
        <span class="ident">p2</span>.<span class="number">1</span>.<span class="ident">min</span>(<span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),
    )
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L556-L590</em></p>
<p><code>map_physical_to_virtual</code> is the function that maps the Actual (Unnormalised) Physical (X, Y) Coordinates to the Virtual (X, Y) Coordinates.</p>
<p>This function normalises the coordinates from the four quadrants of the screen into the lower right quadrant. Hence it flips and unflips the coordinates before and after calling <code>map_physical_to_virtual_normalised</code>. (Just like roti prata)</p>
<h1 id="map-virtual-pixels-to-physicalpixels" class="section-header"><a href="#map-virtual-pixels-to-physicalpixels">16 Map Virtual Pixels to Physical Pixels</a></h1>
<p>Here's how we embed <code>VIRTUAL_TO_PHYSICAL_MAP</code>, the array that maps every Virtual Pixel to the corresponding Physical Bounding Box... (The box that contains all Physical Pixels that map to the same Virtual Pixel)</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For each Virtual (x,y) Coordinate, return the Bounding Box (left, top, right, bottom) that encloses the corresponding Physical (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Physical Pixels to redraw when a Virtual Pixel is updated.</span>
<span class="doccomment">/// (x,y) must belong to the X &gt;= 0, Y &gt;= 0 quadrant</span>
<span class="kw">fn</span> <span class="ident">map_virtual_to_physical_normalised</span>(<span class="ident">x</span>: <span class="ident">u8</span>, <span class="ident">y</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> (<span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>) {
    <span class="kw">let</span> <span class="ident">x_index</span> <span class="op">=</span> <span class="ident">x</span>.<span class="ident">min</span>(<span class="ident">VIRTUAL_TO_PHYSICAL_MAP_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="kw">let</span> <span class="ident">y_index</span> <span class="op">=</span> <span class="ident">y</span>.<span class="ident">min</span>(<span class="ident">VIRTUAL_TO_PHYSICAL_MAP_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="kw">let</span> <span class="ident">physical_box</span> <span class="op">=</span> <span class="ident">VIRTUAL_TO_PHYSICAL_MAP</span>[<span class="ident">y_index</span> <span class="kw">as</span> <span class="ident">usize</span>][<span class="ident">x_index</span> <span class="kw">as</span> <span class="ident">usize</span>];  <span class="comment">//  Returns (left,top,right,bottom)</span>
    <span class="ident">physical_box</span>
}

<span class="doccomment">/// For each Virtual (x,y) Coordinate, return the Bounding Box (left, top, right, bottom) that encloses the corresponding Physical (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Physical Pixels to redraw when a Virtual Pixel is updated.</span>
<span class="doccomment">/// Since X and Y are symmetric, this grid only covers one quadrant (X &gt;= 0, Y &gt;= 0)</span>
<span class="kw">static</span> <span class="ident">VIRTUAL_TO_PHYSICAL_MAP</span>: <span class="kw-2">&amp;</span>[[(<span class="ident">u8</span>,<span class="ident">u8</span>,<span class="ident">u8</span>,<span class="ident">u8</span>); <span class="ident">VIRTUAL_TO_PHYSICAL_MAP_WIDTH</span>]; <span class="ident">VIRTUAL_TO_PHYSICAL_MAP_HEIGHT</span>] <span class="op">=</span> <span class="op">&amp;</span>  <span class="comment">//  Row=Y, Col=X</span>
[                      <span class="comment">//  Copied from output of https://github.com/lupyuen/interpolate-surface</span>
    [                  <span class="comment">//  Virtual Row 0</span>
        (<span class="number">0</span>,<span class="number">0</span>,<span class="number">4</span>,<span class="number">6</span>),     <span class="comment">//  Virtual Row 0, Col 0    =&gt; Physical Left Col 0,   Top Row 0, Right Col 4,  Bottom Row 6</span>
        (<span class="number">5</span>,<span class="number">0</span>,<span class="number">8</span>,<span class="number">6</span>),     <span class="comment">//  Virtual Row 0, Col 1    =&gt; Physical Left Col 5,   Top Row 0, Right Col 8,  Bottom Row 6</span>
        ...
        (<span class="number">116</span>,<span class="number">0</span>,<span class="number">119</span>,<span class="number">4</span>), <span class="comment">//  Virtual Row 0, Col 31   =&gt; Physical Left Col 116, Top Row 0, Right Col 119, Bottom Row 4</span>
    ],    
    [                  <span class="comment">//  Virtual Row 1</span>
        (<span class="number">0</span>,<span class="number">7</span>,<span class="number">4</span>,<span class="number">12</span>),    <span class="comment">//  Virtual Row 1, Col 0    =&gt; Physical Left Col 0,   Top Row 7, Right Col 4,  Bottom Row 12</span>
        (<span class="number">5</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">12</span>),    <span class="comment">//  Virtual Row 1, Col 1    =&gt; Physical Left Col 5,   Top Row 7, Right Col 8,  Bottom Row 12</span>
        ...
        (<span class="number">116</span>,<span class="number">5</span>,<span class="number">119</span>,<span class="number">9</span>), <span class="comment">//  Virtual Row 1, Col 31   =&gt; Physical Left Col 116, Top Row 5, Right Col 119, Bottom Row 9</span>
    ],
...</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L654-L782</em></p>
<p><code>VIRTUAL_TO_PHYSICAL_MAP</code> is the Lookup Table precomputed by the <code>generate_virtual_to_physical_map</code> function that we have seen earlier. This is a 2D array with 16 rows and 32 columns, covering the Virtual Pixels in the lower right quadrant of the CHIP-8 Emulator.</p>
<p>Each element of <code>VIRTUAL_TO_PHYSICAL_MAP</code> (indexed by Virtual Row and Virtual Column) is a tuple (Physical Left, Physical Top, Physical Right, Physical Bottom), the coordinates of the mapped Physical Bounding Box on the PineTime display.</p>
<p><code>map_virtual_to_physical_normalised</code> is the function used to look up the <code>VIRTUAL_TO_PHYSICAL_MAP</code> table by Normalised Virtual (X, Y) Coordinates.</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// For each Virtual (x,y) Coordinate, return the Bounding Box (left, top, right, bottom) that encloses the corresponding Physical (x,y) Coordinates.</span>
<span class="doccomment">/// Used by the CHIP-8 Emulator to decide which Physical Pixels to redraw when a Virtual Pixel is updated.</span>
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;chip8_curve&quot;</span>)]</span>  <span class="comment">//  If we are rendering CHIP8 Emulator as curved surface...</span>
<span class="kw">fn</span> <span class="ident">map_virtual_to_physical</span>(<span class="ident">x</span>: <span class="ident">u8</span>, <span class="ident">y</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> (<span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>) {
    <span class="comment">//  Check which quadrant (x,y) belongs to and flip accordingly</span>
    <span class="kw">let</span> <span class="ident">flip</span> <span class="op">=</span>  <span class="comment">//  (flip for X, flip for Y)</span>
        <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">true</span>, <span class="bool-val">true</span>)  <span class="comment">//  Top left quadrant: Flip horizontally and vertically</span>
        } <span class="kw">else</span> <span class="kw">if</span> <span class="ident">x</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&lt;</span> <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">false</span>, <span class="bool-val">true</span>)   <span class="comment">//  Top right quadrant: Flip vertically</span>
        } <span class="kw">else</span> <span class="kw">if</span> <span class="ident">x</span> <span class="op">&lt;</span> <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">y</span> <span class="op">&gt;</span><span class="op">=</span> <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> {
            (<span class="bool-val">true</span>, <span class="bool-val">false</span>)   <span class="comment">//  Bottom left quadrant: Flip horizontally</span>
        } <span class="kw">else</span> {
            (<span class="bool-val">false</span>, <span class="bool-val">false</span>)    <span class="comment">//  Bottom right quadrant: Don&#39;t flip</span>
        };
    <span class="kw">let</span> <span class="ident">x_normalised</span> <span class="op">=</span> 
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">0</span> { <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">x</span> } 
        <span class="kw">else</span>      { <span class="ident">x</span> <span class="op">-</span> <span class="ident">SCREEN_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> };
    <span class="kw">let</span> <span class="ident">y_normalised</span> <span class="op">=</span> 
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">1</span> { <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">y</span> }
        <span class="kw">else</span>      { <span class="ident">y</span> <span class="op">-</span> <span class="ident">SCREEN_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> };
    <span class="kw">let</span> <span class="ident">b</span> <span class="op">=</span> <span class="ident">map_virtual_to_physical_normalised</span>(<span class="ident">x_normalised</span>, <span class="ident">y_normalised</span>);  <span class="comment">//  Returns (left,top,right,bottom)</span>
    <span class="kw">let</span> <span class="ident">b2</span> <span class="op">=</span> (
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">0</span> { <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">b</span>.<span class="number">0</span> } 
        <span class="kw">else</span>      { <span class="ident">b</span>.<span class="number">0</span> <span class="op">+</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
        ,
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">1</span> { <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">b</span>.<span class="number">1</span> } 
        <span class="kw">else</span>      { <span class="ident">b</span>.<span class="number">1</span> <span class="op">+</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
        ,
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">0</span> { <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">b</span>.<span class="number">2</span> } 
        <span class="kw">else</span>      { <span class="ident">b</span>.<span class="number">2</span> <span class="op">+</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
        ,
        <span class="kw">if</span> <span class="ident">flip</span>.<span class="number">1</span> { <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> <span class="op">-</span> <span class="ident">b</span>.<span class="number">3</span> } 
        <span class="kw">else</span>      { <span class="ident">b</span>.<span class="number">3</span> <span class="op">+</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">/</span> <span class="number">2</span> }
    );
    <span class="comment">//  Crop to screen size</span>
    <span class="kw">let</span> <span class="ident">crop</span> <span class="op">=</span> (
        <span class="ident">b2</span>.<span class="number">0</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),   <span class="comment">//  Left</span>
        <span class="ident">b2</span>.<span class="number">1</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),  <span class="comment">//  Top</span>
        <span class="ident">b2</span>.<span class="number">2</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),   <span class="comment">//  Right</span>
        <span class="ident">b2</span>.<span class="number">3</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>),  <span class="comment">//  Bottom</span>
    );
    <span class="comment">//  Flip left and right, top and bottom if necessary</span>
    <span class="kw">let</span> <span class="ident">result</span> <span class="op">=</span> (
        <span class="ident">crop</span>.<span class="number">0</span>.<span class="ident">min</span>(<span class="ident">crop</span>.<span class="number">2</span>),  <span class="comment">//  Left</span>
        <span class="ident">crop</span>.<span class="number">1</span>.<span class="ident">min</span>(<span class="ident">crop</span>.<span class="number">3</span>),  <span class="comment">//  Top</span>
        <span class="ident">crop</span>.<span class="number">0</span>.<span class="ident">max</span>(<span class="ident">crop</span>.<span class="number">2</span>),  <span class="comment">//  Right</span>
        <span class="ident">crop</span>.<span class="number">1</span>.<span class="ident">max</span>(<span class="ident">crop</span>.<span class="number">3</span>),  <span class="comment">//  Bottom</span>
    );
    <span class="macro">assert</span><span class="macro">!</span>(<span class="ident">result</span>.<span class="number">0</span> <span class="op">&lt;</span><span class="op">=</span> <span class="ident">result</span>.<span class="number">2</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">result</span>.<span class="number">1</span> <span class="op">&lt;</span><span class="op">=</span> <span class="ident">result</span>.<span class="number">3</span>, <span class="string">&quot;flip error&quot;</span>);  <span class="comment">//  Left &lt;= Right and Top &lt;= Bottom</span>
    <span class="ident">result</span>
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L592-L643</em></p>
<p><code>map_virtual_to_physical</code> is the function that maps the Actual (Unnormalised) Virtual (X, Y) Coordinates to the Physical (Left, Top, Right, Bottom) Bounding Box.</p>
<p>This function normalises the coordinates from the four quadrants of the screen into the lower right quadrant. Hence it flips and unflips the coordinates before and after calling <code>map_virtual_to_physical_normalised</code>. (Just like pizza)</p>
<h1 id="iterate-curvedpixels" class="section-header"><a href="#iterate-curvedpixels">17 Iterate Curved Pixels</a></h1>
<p><em>How shall we use the two Lookup Tables and their access functions <code>map_physical_to_virtual</code>, <code>map_virtual_to_physical</code>?</em></p>
<p>Remember <code>PixelIterator</code>, our Rust Iterator that returns a sequence of Physical Pixel Colours (16-bit) that will be rendered for a Physical Block of PineTime pixels?</p>
<p>Here's the updated <code>PixelIterator</code> for rendering pixels on a curved surface...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Implement the Iterator for Virtual Pixels in a Virtual Block</span>
<span class="kw">impl</span> <span class="ident">Iterator</span> <span class="kw">for</span> <span class="ident">PixelIterator</span> {
    <span class="doccomment">/// This Iterator returns Physical Pixel colour words (16-bit)</span>
    <span class="kw">type</span> <span class="ident">Item</span> <span class="op">=</span> <span class="ident">u16</span>;
    ...    
    <span class="doccomment">/// Return the next Physical Pixel colour</span>
    <span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;chip8_curve&quot;</span>)]</span>  <span class="comment">//  If we are rendering CHIP8 Emulator as curved surface...</span>
    <span class="kw">fn</span> <span class="ident">next</span>(<span class="kw-2">&amp;</span><span class="kw-2">mut</span> <span class="self">self</span>) <span class="op">-</span><span class="op">&gt;</span> <span class="prelude-ty">Option</span><span class="op">&lt;</span><span class="self">Self</span>::<span class="ident">Item</span><span class="op">&gt;</span> {
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">y_physical</span> <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">physical_bottom</span> { <span class="kw">return</span> <span class="prelude-val">None</span>; }  <span class="comment">//  No more Physical Pixels</span>
        <span class="macro">assert</span><span class="macro">!</span>(<span class="self">self</span>.<span class="ident">x_physical</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span>, <span class="string">&quot;x overflow&quot;</span>);
        <span class="macro">assert</span><span class="macro">!</span>(<span class="self">self</span>.<span class="ident">y_physical</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span>, <span class="string">&quot;y overflow&quot;</span>);

        <span class="comment">//  Map the Physical Pixel to the Virtual Pixel</span>
        <span class="kw">let</span> <span class="ident">virtual_pixel</span> <span class="op">=</span> <span class="ident">map_physical_to_virtual</span>(<span class="self">self</span>.<span class="ident">x_physical</span>, <span class="self">self</span>.<span class="ident">y_physical</span>);

        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">x</span> <span class="op">=</span><span class="op">=</span> <span class="ident">virtual_pixel</span>.<span class="number">0</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="self">self</span>.<span class="ident">y</span> <span class="op">=</span><span class="op">=</span> <span class="ident">virtual_pixel</span>.<span class="number">1</span> {
            <span class="comment">//  If rendering the same Virtual Pixel, increment the offset</span>
            <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
        } <span class="kw">else</span> {
            <span class="comment">//  If rendering a different Virtual Pixel, reset the offset</span>
            <span class="self">self</span>.<span class="ident">x</span> <span class="op">=</span> <span class="ident">virtual_pixel</span>.<span class="number">0</span>;
            <span class="self">self</span>.<span class="ident">y</span> <span class="op">=</span> <span class="ident">virtual_pixel</span>.<span class="number">1</span>;
            <span class="self">self</span>.<span class="ident">x_offset</span> <span class="op">=</span> <span class="number">0</span>;
            <span class="self">self</span>.<span class="ident">y_offset</span> <span class="op">=</span> <span class="number">0</span>;
        }

        <span class="comment">//  Get the colour from the Virtual Screen Buffer</span>
        <span class="kw">let</span> <span class="ident">color</span> <span class="op">=</span> <span class="self">self</span>.<span class="ident">get_color</span>();

        <span class="comment">//  Loop over x_physical from physical_left to physical_right</span>
        <span class="self">self</span>.<span class="ident">x_physical</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
        <span class="kw">if</span> <span class="self">self</span>.<span class="ident">x_physical</span> <span class="op">&gt;</span> <span class="self">self</span>.<span class="ident">physical_right</span> {
            <span class="self">self</span>.<span class="ident">x_physical</span> <span class="op">=</span> <span class="self">self</span>.<span class="ident">physical_left</span>;
            <span class="comment">//  Loop over y_physical from physical_top to physical_bottom</span>
            <span class="self">self</span>.<span class="ident">y_physical</span> <span class="op">+</span><span class="op">=</span> <span class="number">1</span>;
        }
        
        <span class="comment">//  Return the Physical Pixel color</span>
        <span class="kw">return</span> <span class="prelude-val">Some</span>(<span class="ident">color</span>);
    }    </pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L457-L491</em></p>
<p>The new <code>PixelIterator</code> steps through each Physical PineTime Pixel in a block and calls <code>map_physical_to_virtual</code> to find the corresponding Virtual CHIP-8 Pixel and its colour.</p>
<p>With curved distortion, a Virtual CHIP-8 Pixel no longer maps to a rectangular block of Physical PineTime Pixels... It actually maps to a curved block of Physical Pixels.</p>
<p>To simplify the rendering, we'll just consider the Bounding Box of the Physical Pixels. Which may overlap partially with other Virtual Pixels... But the extra rendering should be fine.</p>
<p>Here's how we get the Bounding Box of Physical Pixels for a Virtual Pixel...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="doccomment">/// Return Bounding Box of Physical Pixels (left, top, right, bottom) that correspond to the Virtual Pixels</span>
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">&quot;chip8_curve&quot;</span>)]</span>  <span class="comment">//  If we are rendering CHIP8 Emulator as curved surface...</span>
<span class="kw">fn</span> <span class="ident">get_bounding_box</span>(<span class="ident">virtual_left</span>: <span class="ident">u8</span>, <span class="ident">virtual_top</span>: <span class="ident">u8</span>, <span class="ident">virtual_right</span>: <span class="ident">u8</span>, <span class="ident">virtual_bottom</span>: <span class="ident">u8</span>) <span class="op">-</span><span class="op">&gt;</span> (<span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>, <span class="ident">u8</span>) {
    <span class="comment">//  One Virtual Pixel may map to multiple Physical Pixels, so we lookup the Physical Bounding Box.</span>
    <span class="comment">//  TODO: Handle wide and tall Bounding Boxes</span>
    <span class="kw">let</span> <span class="ident">physical_left_top</span> <span class="op">=</span> <span class="ident">map_virtual_to_physical</span>(<span class="ident">virtual_left</span>, <span class="ident">virtual_top</span>);  <span class="comment">//  Returns (left,top,right,bottom)</span>
    <span class="kw">let</span> <span class="ident">physical_right_bottom</span> <span class="op">=</span> <span class="ident">map_virtual_to_physical</span>(<span class="ident">virtual_right</span>, <span class="ident">virtual_bottom</span>);

    <span class="kw">let</span> <span class="ident">left</span>: <span class="ident">u8</span> <span class="op">=</span> <span class="ident">physical_left_top</span>.<span class="number">0</span>;
    <span class="kw">let</span> <span class="ident">top</span>: <span class="ident">u8</span> <span class="op">=</span> <span class="ident">physical_left_top</span>.<span class="number">1</span>;
    <span class="kw">let</span> <span class="ident">right</span>: <span class="ident">u8</span> <span class="op">=</span> <span class="ident">physical_right_bottom</span>.<span class="number">2</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="kw">let</span> <span class="ident">bottom</span>: <span class="ident">u8</span> <span class="op">=</span> <span class="ident">physical_right_bottom</span>.<span class="number">3</span>.<span class="ident">min</span>(<span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="op">-</span> <span class="number">1</span>);
    <span class="macro">assert</span><span class="macro">!</span>(<span class="ident">left</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">top</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">right</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_WIDTH</span> <span class="kw">as</span> <span class="ident">u8</span> <span class="kw-2">&amp;</span><span class="op">&amp;</span> <span class="ident">bottom</span> <span class="op">&lt;</span> <span class="ident">PHYSICAL_HEIGHT</span> <span class="kw">as</span> <span class="ident">u8</span>, <span class="string">&quot;overflow&quot;</span>);
    ( <span class="ident">left</span>, <span class="ident">top</span>, <span class="ident">right</span>, <span class="ident">bottom</span> )  <span class="comment">//  Return the Physical Bounding Box</span>
}</pre></div>
<p><em>From https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/rust/app/src/chip8.rs#L523-L538</em></p>
<p>The above curved version of <code>get_bounding_box</code> is called by <code>render_region</code> to determine which Physical Pixels need to be redrawn whenever the CHIP-8 screen is updated.</p>
<h1 id="lookup-table-size" class="section-header"><a href="#lookup-table-size">18 Lookup Table Size</a></h1>
<p><em>Will this curved distortion for CHIP-8 bloat the PineTime firmware size? Will the Lookup Tables for the curved mapping fit comfortably into PineTime's Flash ROM (512 KB)?</em></p>
<p><img src="https://lupyuen.github.io/images/chip8-size.png" alt="PineTime Firmware Size without distortion (left) and with curved distortion (right)" /></p>
<p><em>PineTime Firmware Size without distortion (left) and with curved distortion (right)</em></p>
<p>Amazingly... NO not much bloat, and YES the tables fit into ROM! Only <strong>27 KB</strong> of Flash ROM was needed to store the Lookup Tables! (No extra RAM needed)</p>
<p>Take a look at the demo video... Rendering CHIP-8 on a curved surface doesn't seem to affect the game performance. Lookup Tables in ROM work really well for curved rendering!</p>
<p><img src="https://lupyuen.github.io/images/chip8-blinky-curve.jpg" alt="Blinky distorted on a curved surface" /></p>
<p>▶️ <a href="https://youtu.be/TlP-CQfDOwY"><em>Watch the video</em></a></p>
<p>▶️ <a href="https://vt.tiktok.com/2KHwVE/"><em>抖音视频</em></a></p>
<h1 id="further-reading" class="section-header"><a href="#further-reading">19 Further Reading</a></h1>
<p><a href="https://github.com/lupyuen/pinetime-rust-mynewt/blob/master/README.md">Check out the other PineTime articles</a></p>

    
</body>
</html>